diff options
Diffstat (limited to 'security/nss/lib/certdb/pcertdb.c')
-rw-r--r-- | security/nss/lib/certdb/pcertdb.c | 7292 |
1 files changed, 7292 insertions, 0 deletions
diff --git a/security/nss/lib/certdb/pcertdb.c b/security/nss/lib/certdb/pcertdb.c new file mode 100644 index 000000000..aef986c01 --- /dev/null +++ b/security/nss/lib/certdb/pcertdb.c @@ -0,0 +1,7292 @@ +/* + * 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. + */ + +/* + * Permanent Certificate database handling code + * + * $Id$ + */ +#include "prtime.h" + +#include "cert.h" +#include "mcom_db.h" +#include "certdb.h" +#include "secitem.h" +#include "secder.h" + +/* Call to PK11_FreeSlot below */ + +#include "secasn1.h" +#include "secerr.h" +#include "prlock.h" +#include "prmon.h" +#include "nsslocks.h" +#include "base64.h" +#include "sechash.h" +#include "plhash.h" + +#include "cdbhdl.h" + +/* + * the following functions are wrappers for the db library that implement + * a global lock to make the database thread safe. + */ +static PRLock *dbLock = NULL; + +void +certdb_InitDBLock(void) +{ + if (dbLock == NULL) { + nss_InitLock(&dbLock); + PORT_Assert(dbLock != NULL); + } + + return; +} + +static int +certdb_Get(DB *db, DBT *key, DBT *data, unsigned int flags) +{ + PRStatus prstat; + int ret; + + PORT_Assert(dbLock != NULL); + PR_Lock(dbLock); + + ret = (* db->get)(db, key, data, flags); + + prstat = PR_Unlock(dbLock); + + return(ret); +} + +static int +certdb_Put(DB *db, DBT *key, DBT *data, unsigned int flags) +{ + PRStatus prstat; + int ret; + + PORT_Assert(dbLock != NULL); + PR_Lock(dbLock); + + ret = (* db->put)(db, key, data, flags); + + prstat = PR_Unlock(dbLock); + + return(ret); +} + +static int +certdb_Sync(DB *db, unsigned int flags) +{ + PRStatus prstat; + int ret; + + PORT_Assert(dbLock != NULL); + PR_Lock(dbLock); + + ret = (* db->sync)(db, flags); + + prstat = PR_Unlock(dbLock); + + return(ret); +} + +static int +certdb_Del(DB *db, DBT *key, unsigned int flags) +{ + PRStatus prstat; + int ret; + + PORT_Assert(dbLock != NULL); + PR_Lock(dbLock); + + ret = (* db->del)(db, key, flags); + + prstat = PR_Unlock(dbLock); + + return(ret); +} + +static int +certdb_Seq(DB *db, DBT *key, DBT *data, unsigned int flags) +{ + PRStatus prstat; + int ret; + + PORT_Assert(dbLock != NULL); + PR_Lock(dbLock); + + ret = (* db->seq)(db, key, data, flags); + + prstat = PR_Unlock(dbLock); + + return(ret); +} + +static void +certdb_Close(DB *db) +{ + PRStatus prstat; + + PORT_Assert(dbLock != NULL); + PR_Lock(dbLock); + + (* db->close)(db); + + prstat = PR_Unlock(dbLock); + + return; +} + +/* forward references */ +static void CERT_DestroyCertificateNoLocking(CERTCertificate *cert); +static SECStatus AddCertToSPKDigestTable(CERTCertDBHandle *handle, + CERTCertificate *cert); +static SECStatus RemoveCertFromSPKDigestTable(CERTCertDBHandle *handle, + CERTCertificate *cert); + +static SECStatus +DeleteDBEntry(CERTCertDBHandle *handle, certDBEntryType type, SECItem *dbkey) +{ + DBT key; + int ret; + + /* init the database key */ + key.data = dbkey->data; + key.size = dbkey->len; + + dbkey->data[0] = (unsigned char)type; + + /* delete entry from database */ + ret = certdb_Del(handle->permCertDB, &key, 0 ); + if ( ret != 0 ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + ret = certdb_Sync(handle->permCertDB, 0); + if ( ret ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + return(SECSuccess); + +loser: + return(SECFailure); +} + +static SECStatus +ReadDBEntry(CERTCertDBHandle *handle, certDBEntryCommon *entry, + SECItem *dbkey, SECItem *dbentry, PRArenaPool *arena) +{ + DBT data, key; + int ret; + unsigned char *buf; + + /* init the database key */ + key.data = dbkey->data; + key.size = dbkey->len; + + dbkey->data[0] = (unsigned char)entry->type; + + /* read entry from database */ + ret = certdb_Get(handle->permCertDB, &key, &data, 0 ); + if ( ret != 0 ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + /* validate the entry */ + if ( data.size < SEC_DB_ENTRY_HEADER_LEN ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + buf = (unsigned char *)data.data; + if ( buf[0] != (unsigned char)CERT_DB_FILE_VERSION ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + if ( buf[1] != (unsigned char)entry->type ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + /* copy out header information */ + entry->version = (unsigned int)buf[0]; + entry->type = (certDBEntryType)buf[1]; + entry->flags = (unsigned int)buf[2]; + + /* format body of entry for return to caller */ + dbentry->len = data.size - SEC_DB_ENTRY_HEADER_LEN; + if ( dbentry->len ) { + dbentry->data = (unsigned char *)PORT_ArenaAlloc(arena, dbentry->len); + if ( dbentry->data == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + PORT_Memcpy(dbentry->data, &buf[SEC_DB_ENTRY_HEADER_LEN], + dbentry->len); + } else { + dbentry->data = NULL; + } + + return(SECSuccess); + +loser: + return(SECFailure); +} + +/** + ** Implement low level database access + **/ +static SECStatus +WriteDBEntry(CERTCertDBHandle *handle, certDBEntryCommon *entry, + SECItem *dbkey, SECItem *dbentry) +{ + int ret; + DBT data, key; + unsigned char *buf; + + data.data = dbentry->data; + data.size = dbentry->len; + + buf = (unsigned char*)data.data; + + buf[0] = (unsigned char)entry->version; + buf[1] = (unsigned char)entry->type; + buf[2] = (unsigned char)entry->flags; + + key.data = dbkey->data; + key.size = dbkey->len; + + dbkey->data[0] = (unsigned char)entry->type; + + /* put the record into the database now */ + ret = certdb_Put(handle->permCertDB, &key, &data, 0); + + if ( ret != 0 ) { + goto loser; + } + + ret = certdb_Sync( handle->permCertDB, 0 ); + + if ( ret ) { + goto loser; + } + + return(SECSuccess); + +loser: + return(SECFailure); +} + +/* + * encode a database cert record + */ +static SECStatus +EncodeDBCertEntry(certDBEntryCert *entry, PRArenaPool *arena, SECItem *dbitem) +{ + unsigned int nnlen; + unsigned char *buf; + char *nn; + char zbuf = 0; + + if ( entry->nickname ) { + nn = entry->nickname; + } else { + nn = &zbuf; + } + nnlen = PORT_Strlen(nn) + 1; + + /* allocate space for encoded database record, including space + * for low level header + */ + dbitem->len = entry->derCert.len + nnlen + DB_CERT_ENTRY_HEADER_LEN + + SEC_DB_ENTRY_HEADER_LEN; + + dbitem->data = (unsigned char *)PORT_ArenaAlloc(arena, dbitem->len); + if ( dbitem->data == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* fill in database record */ + buf = &dbitem->data[SEC_DB_ENTRY_HEADER_LEN]; + + buf[0] = ( entry->trust.sslFlags >> 8 ) & 0xff; + buf[1] = entry->trust.sslFlags & 0xff; + buf[2] = ( entry->trust.emailFlags >> 8 ) & 0xff; + buf[3] = entry->trust.emailFlags & 0xff; + buf[4] = ( entry->trust.objectSigningFlags >> 8 ) & 0xff; + buf[5] = entry->trust.objectSigningFlags & 0xff; + buf[6] = ( entry->derCert.len >> 8 ) & 0xff; + buf[7] = entry->derCert.len & 0xff; + buf[8] = ( nnlen >> 8 ) & 0xff; + buf[9] = nnlen & 0xff; + + PORT_Memcpy(&buf[DB_CERT_ENTRY_HEADER_LEN], entry->derCert.data, + entry->derCert.len); + + PORT_Memcpy(&buf[DB_CERT_ENTRY_HEADER_LEN + entry->derCert.len], + nn, nnlen); + + return(SECSuccess); + +loser: + return(SECFailure); +} + +/* + * encode a database key for a cert record + */ +static SECStatus +EncodeDBCertKey(SECItem *certKey, PRArenaPool *arena, SECItem *dbkey) +{ + dbkey->len = certKey->len + SEC_DB_KEY_HEADER_LEN; + dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey->len); + if ( dbkey->data == NULL ) { + goto loser; + } + PORT_Memcpy(&dbkey->data[SEC_DB_KEY_HEADER_LEN], + certKey->data, certKey->len); + dbkey->data[0] = certDBEntryTypeCert; + + return(SECSuccess); +loser: + return(SECFailure); +} + +static SECStatus +EncodeDBGenericKey(SECItem *certKey, PRArenaPool *arena, SECItem *dbkey, + certDBEntryType entryType) +{ + /* + * we only allow _one_ KRL key! + */ + if (entryType == certDBEntryTypeKeyRevocation) { + dbkey->len = SEC_DB_KEY_HEADER_LEN; + dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey->len); + if ( dbkey->data == NULL ) { + goto loser; + } + dbkey->data[0] = (unsigned char) entryType; + return(SECSuccess); + } + + + dbkey->len = certKey->len + SEC_DB_KEY_HEADER_LEN; + dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey->len); + if ( dbkey->data == NULL ) { + goto loser; + } + PORT_Memcpy(&dbkey->data[SEC_DB_KEY_HEADER_LEN], + certKey->data, certKey->len); + dbkey->data[0] = (unsigned char) entryType; + + return(SECSuccess); +loser: + return(SECFailure); +} + +static SECStatus +DecodeDBCertEntry(certDBEntryCert *entry, SECItem *dbentry) +{ + unsigned int nnlen; + int headerlen; + int lenoff; + + /* allow updates of old versions of the database */ + switch ( entry->common.version ) { + case 5: + headerlen = DB_CERT_V5_ENTRY_HEADER_LEN; + lenoff = 3; + break; + case 6: + /* should not get here */ + PORT_Assert(0); + headerlen = DB_CERT_V6_ENTRY_HEADER_LEN; + lenoff = 3; + break; + case 7: + headerlen = DB_CERT_ENTRY_HEADER_LEN; + lenoff = 6; + break; + default: + /* better not get here */ + PORT_Assert(0); + headerlen = DB_CERT_V5_ENTRY_HEADER_LEN; + lenoff = 3; + break; + } + + /* is record long enough for header? */ + if ( dbentry->len < headerlen ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + /* is database entry correct length? */ + entry->derCert.len = ( ( dbentry->data[lenoff] << 8 ) | + dbentry->data[lenoff+1] ); + nnlen = ( ( dbentry->data[lenoff+2] << 8 ) | dbentry->data[lenoff+3] ); + if ( ( entry->derCert.len + nnlen + headerlen ) + != dbentry->len) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + /* copy the dercert */ + entry->derCert.data = (unsigned char *)PORT_ArenaAlloc(entry->common.arena, + entry->derCert.len); + if ( entry->derCert.data == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + PORT_Memcpy(entry->derCert.data, &dbentry->data[headerlen], + entry->derCert.len); + + /* copy the nickname */ + if ( nnlen > 1 ) { + entry->nickname = (char *)PORT_ArenaAlloc(entry->common.arena, nnlen); + if ( entry->nickname == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + PORT_Memcpy(entry->nickname, + &dbentry->data[headerlen + + entry->derCert.len], + nnlen); + } else { + entry->nickname = NULL; + } + + if ( entry->common.version < 7 ) { + /* allow updates of v5 db */ + entry->trust.sslFlags = dbentry->data[0]; + entry->trust.emailFlags = dbentry->data[1]; + entry->trust.objectSigningFlags = dbentry->data[2]; + } else { + entry->trust.sslFlags = ( dbentry->data[0] << 8 ) | dbentry->data[1]; + entry->trust.emailFlags = ( dbentry->data[2] << 8 ) | dbentry->data[3]; + entry->trust.objectSigningFlags = + ( dbentry->data[4] << 8 ) | dbentry->data[5]; + } + + return(SECSuccess); +loser: + return(SECFailure); +} + + +/* + * Create a new certDBEntryCert from existing data + */ +static certDBEntryCert * +NewDBCertEntry(SECItem *derCert, char *nickname, + CERTCertTrust *trust, int flags) +{ + certDBEntryCert *entry; + PRArenaPool *arena = NULL; + int nnlen; + + arena = PORT_NewArena( DER_DEFAULT_CHUNKSIZE ); + + if ( !arena ) { + goto loser; + } + + entry = (certDBEntryCert *)PORT_ArenaZAlloc(arena, sizeof(certDBEntryCert)); + + if ( entry == NULL ) { + goto loser; + } + + /* fill in the dbCert */ + entry->common.arena = arena; + entry->common.type = certDBEntryTypeCert; + entry->common.version = CERT_DB_FILE_VERSION; + entry->common.flags = flags; + + if ( trust ) { + entry->trust = *trust; + } + + entry->derCert.data = (unsigned char *)PORT_ArenaAlloc(arena, derCert->len); + if ( !entry->derCert.data ) { + goto loser; + } + entry->derCert.len = derCert->len; + PORT_Memcpy(entry->derCert.data, derCert->data, derCert->len); + + nnlen = ( nickname ? strlen(nickname) + 1 : 0 ); + + if ( nnlen ) { + entry->nickname = (char *)PORT_ArenaAlloc(arena, nnlen); + if ( !entry->nickname ) { + goto loser; + } + PORT_Memcpy(entry->nickname, nickname, nnlen); + + } else { + entry->nickname = 0; + } + + return(entry); + +loser: + + /* allocation error, free arena and return */ + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + PORT_SetError(SEC_ERROR_NO_MEMORY); + return(0); +} + +/* + * Decode a version 4 DBCert from the byte stream database format + * and construct a current database entry struct + */ +static certDBEntryCert * +DecodeV4DBCertEntry(unsigned char *buf, int len) +{ + certDBEntryCert *entry; + int certlen; + int nnlen; + PRArenaPool *arena; + + /* make sure length is at least long enough for the header */ + if ( len < DBCERT_V4_HEADER_LEN ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + return(0); + } + + /* get other lengths */ + certlen = buf[3] << 8 | buf[4]; + nnlen = buf[5] << 8 | buf[6]; + + /* make sure DB entry is the right size */ + if ( ( certlen + nnlen + DBCERT_V4_HEADER_LEN ) != len ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + return(0); + } + + /* allocate arena */ + arena = PORT_NewArena( DER_DEFAULT_CHUNKSIZE ); + + if ( !arena ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return(0); + } + + /* allocate structure and members */ + entry = (certDBEntryCert *) PORT_ArenaAlloc(arena, sizeof(certDBEntryCert)); + + if ( !entry ) { + goto loser; + } + + entry->derCert.data = (unsigned char *)PORT_ArenaAlloc(arena, certlen); + if ( !entry->derCert.data ) { + goto loser; + } + entry->derCert.len = certlen; + + if ( nnlen ) { + entry->nickname = (char *) PORT_ArenaAlloc(arena, nnlen); + if ( !entry->nickname ) { + goto loser; + } + } else { + entry->nickname = 0; + } + + entry->common.arena = arena; + entry->common.version = CERT_DB_FILE_VERSION; + entry->common.type = certDBEntryTypeCert; + entry->common.flags = 0; + entry->trust.sslFlags = buf[0]; + entry->trust.emailFlags = buf[1]; + entry->trust.objectSigningFlags = buf[2]; + + PORT_Memcpy(entry->derCert.data, &buf[DBCERT_V4_HEADER_LEN], certlen); + PORT_Memcpy(entry->nickname, &buf[DBCERT_V4_HEADER_LEN + certlen], nnlen); + + return(entry); + +loser: + PORT_FreeArena(arena, PR_FALSE); + PORT_SetError(SEC_ERROR_NO_MEMORY); + return(0); +} + +/* + * Encode a Certificate database entry into byte stream suitable for + * the database + */ +static SECStatus +WriteDBCertEntry(CERTCertDBHandle *handle, certDBEntryCert *entry) +{ + SECItem dbitem, dbkey; + PRArenaPool *tmparena = NULL; + SECItem tmpitem; + SECStatus rv; + + tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( tmparena == NULL ) { + goto loser; + } + + rv = EncodeDBCertEntry(entry, tmparena, &dbitem); + if ( rv != SECSuccess ) { + goto loser; + } + + /* get the database key and format it */ + rv = CERT_KeyFromDERCert(tmparena, &entry->derCert, &tmpitem); + if ( rv == SECFailure ) { + goto loser; + } + + rv = EncodeDBCertKey(&tmpitem, tmparena, &dbkey); + if ( rv == SECFailure ) { + goto loser; + } + + /* now write it to the database */ + rv = WriteDBEntry(handle, &entry->common, &dbkey, &dbitem); + if ( rv != SECSuccess ) { + goto loser; + } + + PORT_FreeArena(tmparena, PR_FALSE); + return(SECSuccess); + +loser: + if ( tmparena ) { + PORT_FreeArena(tmparena, PR_FALSE); + } + return(SECFailure); +} + + +/* + * delete a certificate entry + */ +static SECStatus +DeleteDBCertEntry(CERTCertDBHandle *handle, SECItem *certKey) +{ + SECItem dbkey; + PRArenaPool *arena = NULL; + SECStatus rv; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + rv = EncodeDBCertKey(certKey, arena, &dbkey); + if ( rv != SECSuccess ) { + goto loser; + } + + rv = DeleteDBEntry(handle, certDBEntryTypeCert, &dbkey); + if ( rv == SECFailure ) { + goto loser; + } + + PORT_FreeArena(arena, PR_FALSE); + return(SECSuccess); + +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(SECFailure); +} + +/* + * Read a certificate entry + */ +static certDBEntryCert * +ReadDBCertEntry(CERTCertDBHandle *handle, SECItem *certKey) +{ + PRArenaPool *arena = NULL; + PRArenaPool *tmparena = NULL; + certDBEntryCert *entry; + SECItem dbkey; + SECItem dbentry; + SECStatus rv; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( tmparena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + entry = (certDBEntryCert *)PORT_ArenaAlloc(arena, sizeof(certDBEntryCert)); + if ( entry == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + entry->common.arena = arena; + entry->common.type = certDBEntryTypeCert; + + rv = EncodeDBCertKey(certKey, tmparena, &dbkey); + if ( rv != SECSuccess ) { + goto loser; + } + + rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena); + if ( rv == SECFailure ) { + goto loser; + } + + rv = DecodeDBCertEntry(entry, &dbentry); + if ( rv != SECSuccess ) { + goto loser; + } + + PORT_FreeArena(tmparena, PR_FALSE); + return(entry); + +loser: + if ( tmparena ) { + PORT_FreeArena(tmparena, PR_FALSE); + } + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(NULL); +} + +/* + * encode a database cert record + */ +static SECStatus +EncodeDBCrlEntry(certDBEntryRevocation *entry, PRArenaPool *arena, SECItem *dbitem) +{ + unsigned int nnlen = 0; + unsigned char *buf; + + if (entry->url) { + nnlen = PORT_Strlen(entry->url) + 1; + } + + /* allocate space for encoded database record, including space + * for low level header + */ + dbitem->len = entry->derCrl.len + nnlen + + SEC_DB_ENTRY_HEADER_LEN + DB_CRL_ENTRY_HEADER_LEN; + + dbitem->data = (unsigned char *)PORT_ArenaAlloc(arena, dbitem->len); + if ( dbitem->data == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* fill in database record */ + buf = &dbitem->data[SEC_DB_ENTRY_HEADER_LEN]; + + buf[0] = ( entry->derCrl.len >> 8 ) & 0xff; + buf[1] = entry->derCrl.len & 0xff; + buf[2] = ( nnlen >> 8 ) & 0xff; + buf[3] = nnlen & 0xff; + + PORT_Memcpy(&buf[DB_CRL_ENTRY_HEADER_LEN], entry->derCrl.data, + entry->derCrl.len); + + if (nnlen != 0) { + PORT_Memcpy(&buf[DB_CRL_ENTRY_HEADER_LEN + entry->derCrl.len], + entry->url, nnlen); + } + + return(SECSuccess); + +loser: + return(SECFailure); +} + +static SECStatus +DecodeDBCrlEntry(certDBEntryRevocation *entry, SECItem *dbentry) +{ + unsigned int nnlen; + + /* is record long enough for header? */ + if ( dbentry->len < DB_CRL_ENTRY_HEADER_LEN ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + /* is database entry correct length? */ + entry->derCrl.len = ( ( dbentry->data[0] << 8 ) | dbentry->data[1] ); + nnlen = ( ( dbentry->data[2] << 8 ) | dbentry->data[3] ); + if ( ( entry->derCrl.len + nnlen + DB_CRL_ENTRY_HEADER_LEN ) + != dbentry->len) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + /* copy the dercert */ + entry->derCrl.data = (unsigned char *)PORT_ArenaAlloc(entry->common.arena, + entry->derCrl.len); + if ( entry->derCrl.data == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + PORT_Memcpy(entry->derCrl.data, &dbentry->data[DB_CRL_ENTRY_HEADER_LEN], + entry->derCrl.len); + + /* copy the url */ + entry->url = NULL; + if (nnlen != 0) { + entry->url = (char *)PORT_ArenaAlloc(entry->common.arena, nnlen); + if ( entry->url == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + PORT_Memcpy(entry->url, + &dbentry->data[DB_CRL_ENTRY_HEADER_LEN + entry->derCrl.len], + nnlen); + } + + return(SECSuccess); +loser: + return(SECFailure); +} + +/* + * Create a new certDBEntryRevocation from existing data + */ +static certDBEntryRevocation * +NewDBCrlEntry(SECItem *derCrl, char * url, certDBEntryType crlType, int flags) +{ + certDBEntryRevocation *entry; + PRArenaPool *arena = NULL; + int nnlen; + + arena = PORT_NewArena( DER_DEFAULT_CHUNKSIZE ); + + if ( !arena ) { + goto loser; + } + + entry = (certDBEntryRevocation*) + PORT_ArenaZAlloc(arena, sizeof(certDBEntryRevocation)); + + if ( entry == NULL ) { + goto loser; + } + + /* fill in the dbRevolcation */ + entry->common.arena = arena; + entry->common.type = crlType; + entry->common.version = CERT_DB_FILE_VERSION; + entry->common.flags = flags; + + + entry->derCrl.data = (unsigned char *)PORT_ArenaAlloc(arena, derCrl->len); + if ( !entry->derCrl.data ) { + goto loser; + } + + if (url) { + nnlen = PORT_Strlen(url) + 1; + entry->url = (char *)PORT_ArenaAlloc(arena, nnlen); + if ( !entry->url ) { + goto loser; + } + PORT_Memcpy(entry->url, url, nnlen); + } else { + entry->url = NULL; + } + + + entry->derCrl.len = derCrl->len; + PORT_Memcpy(entry->derCrl.data, derCrl->data, derCrl->len); + + return(entry); + +loser: + + /* allocation error, free arena and return */ + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + PORT_SetError(SEC_ERROR_NO_MEMORY); + return(0); +} + + +static SECStatus +WriteDBCrlEntry(CERTCertDBHandle *handle, certDBEntryRevocation *entry ) +{ + SECItem dbkey; + PRArenaPool *tmparena = NULL; + SECItem tmpitem,encodedEntry; + SECStatus rv; + + tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( tmparena == NULL ) { + goto loser; + } + + /* get the database key and format it */ + rv = CERT_KeyFromDERCrl(tmparena, &entry->derCrl, &tmpitem); + if ( rv == SECFailure ) { + goto loser; + } + + rv = EncodeDBCrlEntry(entry, tmparena, &encodedEntry); + if ( rv == SECFailure ) { + goto loser; + } + + rv = EncodeDBGenericKey(&tmpitem, tmparena, &dbkey, entry->common.type); + if ( rv == SECFailure ) { + goto loser; + } + + /* now write it to the database */ + rv = WriteDBEntry(handle, &entry->common, &dbkey, &encodedEntry); + if ( rv != SECSuccess ) { + goto loser; + } + + PORT_FreeArena(tmparena, PR_FALSE); + return(SECSuccess); + +loser: + if ( tmparena ) { + PORT_FreeArena(tmparena, PR_FALSE); + } + return(SECFailure); +} +/* + * delete a crl entry + */ +static SECStatus +DeleteDBCrlEntry(CERTCertDBHandle *handle, SECItem *crlKey, + certDBEntryType crlType) +{ + SECItem dbkey; + PRArenaPool *arena = NULL; + SECStatus rv; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + rv = EncodeDBGenericKey(crlKey, arena, &dbkey, crlType); + if ( rv != SECSuccess ) { + goto loser; + } + + rv = DeleteDBEntry(handle, crlType, &dbkey); + if ( rv == SECFailure ) { + goto loser; + } + + PORT_FreeArena(arena, PR_FALSE); + return(SECSuccess); + +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(SECFailure); +} + +/* + * Read a certificate entry + */ +static certDBEntryRevocation * +ReadDBCrlEntry(CERTCertDBHandle *handle, SECItem *certKey, + certDBEntryType crlType) +{ + PRArenaPool *arena = NULL; + PRArenaPool *tmparena = NULL; + certDBEntryRevocation *entry; + SECItem dbkey; + SECItem dbentry; + SECStatus rv; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( tmparena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + entry = (certDBEntryRevocation *) + PORT_ArenaAlloc(arena, sizeof(certDBEntryRevocation)); + if ( entry == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + entry->common.arena = arena; + entry->common.type = crlType; + + rv = EncodeDBGenericKey(certKey, tmparena, &dbkey, crlType); + if ( rv != SECSuccess ) { + goto loser; + } + + rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena); + if ( rv == SECFailure ) { + goto loser; + } + + rv = DecodeDBCrlEntry(entry, &dbentry); + if ( rv != SECSuccess ) { + goto loser; + } + + PORT_FreeArena(tmparena, PR_FALSE); + return(entry); + +loser: + if ( tmparena ) { + PORT_FreeArena(tmparena, PR_FALSE); + } + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(NULL); +} + +/* + * destroy a database entry + */ +static void +DestroyDBEntry(certDBEntry *entry) +{ + PRArenaPool *arena = entry->common.arena; + + /* Zero out the entry struct, so that any further attempts to use it + * will cause an exception (e.g. null pointer reference). */ + PORT_Memset(&entry->common, 0, sizeof entry->common); + PORT_FreeArena(arena, PR_FALSE); + + return; +} + +/* + * Encode a database nickname record + */ +static SECStatus +EncodeDBNicknameEntry(certDBEntryNickname *entry, PRArenaPool *arena, + SECItem *dbitem) +{ + unsigned char *buf; + + /* allocate space for encoded database record, including space + * for low level header + */ + dbitem->len = entry->subjectName.len + DB_NICKNAME_ENTRY_HEADER_LEN + + SEC_DB_ENTRY_HEADER_LEN; + + dbitem->data = (unsigned char *)PORT_ArenaAlloc(arena, dbitem->len); + if ( dbitem->data == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* fill in database record */ + buf = &dbitem->data[SEC_DB_ENTRY_HEADER_LEN]; + + buf[0] = ( entry->subjectName.len >> 8 ) & 0xff; + buf[1] = entry->subjectName.len & 0xff; + + PORT_Memcpy(&buf[DB_NICKNAME_ENTRY_HEADER_LEN], entry->subjectName.data, + entry->subjectName.len); + + return(SECSuccess); + +loser: + return(SECFailure); +} + +/* + * Encode a database key for a nickname record + */ +static SECStatus +EncodeDBNicknameKey(char *nickname, PRArenaPool *arena, + SECItem *dbkey) +{ + unsigned int nnlen; + + nnlen = PORT_Strlen(nickname) + 1; /* includes null */ + + /* now get the database key and format it */ + dbkey->len = nnlen + SEC_DB_KEY_HEADER_LEN; + dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey->len); + if ( dbkey->data == NULL ) { + goto loser; + } + PORT_Memcpy(&dbkey->data[SEC_DB_KEY_HEADER_LEN], nickname, nnlen); + dbkey->data[0] = certDBEntryTypeNickname; + + return(SECSuccess); + +loser: + return(SECFailure); +} + +static SECStatus +DecodeDBNicknameEntry(certDBEntryNickname *entry, SECItem *dbentry) +{ + /* is record long enough for header? */ + if ( dbentry->len < DB_NICKNAME_ENTRY_HEADER_LEN ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + /* is database entry correct length? */ + entry->subjectName.len = ( ( dbentry->data[0] << 8 ) | dbentry->data[1] ); + if (( entry->subjectName.len + DB_NICKNAME_ENTRY_HEADER_LEN ) != + dbentry->len ){ + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + /* copy the certkey */ + entry->subjectName.data = + (unsigned char *)PORT_ArenaAlloc(entry->common.arena, + entry->subjectName.len); + if ( entry->subjectName.data == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + PORT_Memcpy(entry->subjectName.data, + &dbentry->data[DB_NICKNAME_ENTRY_HEADER_LEN], + entry->subjectName.len); + + return(SECSuccess); + +loser: + return(SECFailure); +} + +/* + * create a new nickname entry + */ +static certDBEntryNickname * +NewDBNicknameEntry(char *nickname, SECItem *subjectName, unsigned int flags) +{ + PRArenaPool *arena = NULL; + certDBEntryNickname *entry; + int nnlen; + SECStatus rv; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + entry = (certDBEntryNickname *)PORT_ArenaAlloc(arena, + sizeof(certDBEntryNickname)); + if ( entry == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* init common fields */ + entry->common.arena = arena; + entry->common.type = certDBEntryTypeNickname; + entry->common.version = CERT_DB_FILE_VERSION; + entry->common.flags = flags; + + /* copy the nickname */ + nnlen = PORT_Strlen(nickname) + 1; + + entry->nickname = (char*)PORT_ArenaAlloc(arena, nnlen); + if ( entry->nickname == NULL ) { + goto loser; + } + + PORT_Memcpy(entry->nickname, nickname, nnlen); + + rv = SECITEM_CopyItem(arena, &entry->subjectName, subjectName); + if ( rv != SECSuccess ) { + goto loser; + } + + return(entry); +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(NULL); +} + +/* + * delete a nickname entry + */ +static SECStatus +DeleteDBNicknameEntry(CERTCertDBHandle *handle, char *nickname) +{ + PRArenaPool *arena = NULL; + SECStatus rv; + SECItem dbkey; + + if ( nickname == NULL ) { + return(SECSuccess); + } + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + rv = EncodeDBNicknameKey(nickname, arena, &dbkey); + if ( rv != SECSuccess ) { + goto loser; + } + + rv = DeleteDBEntry(handle, certDBEntryTypeNickname, &dbkey); + if ( rv == SECFailure ) { + goto loser; + } + + PORT_FreeArena(arena, PR_FALSE); + return(SECSuccess); + +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(SECFailure); +} + +/* + * Read a nickname entry + */ +static certDBEntryNickname * +ReadDBNicknameEntry(CERTCertDBHandle *handle, char *nickname) +{ + PRArenaPool *arena = NULL; + PRArenaPool *tmparena = NULL; + certDBEntryNickname *entry; + SECItem dbkey; + SECItem dbentry; + SECStatus rv; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( tmparena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + entry = (certDBEntryNickname *)PORT_ArenaAlloc(arena, + sizeof(certDBEntryNickname)); + if ( entry == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + entry->common.arena = arena; + entry->common.type = certDBEntryTypeNickname; + + rv = EncodeDBNicknameKey(nickname, tmparena, &dbkey); + if ( rv != SECSuccess ) { + goto loser; + } + + rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena); + if ( rv == SECFailure ) { + goto loser; + } + + /* is record long enough for header? */ + if ( dbentry.len < DB_NICKNAME_ENTRY_HEADER_LEN ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + rv = DecodeDBNicknameEntry(entry, &dbentry); + if ( rv != SECSuccess ) { + goto loser; + } + + PORT_FreeArena(tmparena, PR_FALSE); + return(entry); + +loser: + if ( tmparena ) { + PORT_FreeArena(tmparena, PR_FALSE); + } + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(NULL); +} + +/* + * Encode a nickname entry into byte stream suitable for + * the database + */ +static SECStatus +WriteDBNicknameEntry(CERTCertDBHandle *handle, certDBEntryNickname *entry) +{ + SECItem dbitem, dbkey; + PRArenaPool *tmparena = NULL; + SECStatus rv; + + tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( tmparena == NULL ) { + goto loser; + } + + rv = EncodeDBNicknameEntry(entry, tmparena, &dbitem); + if ( rv != SECSuccess ) { + goto loser; + } + + rv = EncodeDBNicknameKey(entry->nickname, tmparena, &dbkey); + if ( rv != SECSuccess ) { + goto loser; + } + + /* now write it to the database */ + rv = WriteDBEntry(handle, &entry->common, &dbkey, &dbitem); + if ( rv != SECSuccess ) { + goto loser; + } + + PORT_FreeArena(tmparena, PR_FALSE); + return(SECSuccess); + +loser: + if ( tmparena ) { + PORT_FreeArena(tmparena, PR_FALSE); + } + return(SECFailure); + +} + +/* + * Encode a database smime record + */ +static SECStatus +EncodeDBSMimeEntry(certDBEntrySMime *entry, PRArenaPool *arena, + SECItem *dbitem) +{ + unsigned char *buf; + + /* allocate space for encoded database record, including space + * for low level header + */ + dbitem->len = entry->subjectName.len + entry->smimeOptions.len + + entry->optionsDate.len + + DB_SMIME_ENTRY_HEADER_LEN + SEC_DB_ENTRY_HEADER_LEN; + + dbitem->data = (unsigned char *)PORT_ArenaAlloc(arena, dbitem->len); + if ( dbitem->data == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* fill in database record */ + buf = &dbitem->data[SEC_DB_ENTRY_HEADER_LEN]; + + buf[0] = ( entry->subjectName.len >> 8 ) & 0xff; + buf[1] = entry->subjectName.len & 0xff; + buf[2] = ( entry->smimeOptions.len >> 8 ) & 0xff; + buf[3] = entry->smimeOptions.len & 0xff; + buf[4] = ( entry->optionsDate.len >> 8 ) & 0xff; + buf[5] = entry->optionsDate.len & 0xff; + + /* if no smime options, then there should not be an options date either */ + PORT_Assert( ! ( ( entry->smimeOptions.len == 0 ) && + ( entry->optionsDate.len != 0 ) ) ); + + PORT_Memcpy(&buf[DB_SMIME_ENTRY_HEADER_LEN], entry->subjectName.data, + entry->subjectName.len); + if ( entry->smimeOptions.len ) { + PORT_Memcpy(&buf[DB_SMIME_ENTRY_HEADER_LEN+entry->subjectName.len], + entry->smimeOptions.data, + entry->smimeOptions.len); + PORT_Memcpy(&buf[DB_SMIME_ENTRY_HEADER_LEN + entry->subjectName.len + + entry->smimeOptions.len], + entry->optionsDate.data, + entry->optionsDate.len); + } + + return(SECSuccess); + +loser: + return(SECFailure); +} + +/* + * Encode a database key for a SMIME record + */ +static SECStatus +EncodeDBSMimeKey(char *emailAddr, PRArenaPool *arena, + SECItem *dbkey) +{ + unsigned int addrlen; + + addrlen = PORT_Strlen(emailAddr) + 1; /* includes null */ + + /* now get the database key and format it */ + dbkey->len = addrlen + SEC_DB_KEY_HEADER_LEN; + dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey->len); + if ( dbkey->data == NULL ) { + goto loser; + } + PORT_Memcpy(&dbkey->data[SEC_DB_KEY_HEADER_LEN], emailAddr, addrlen); + dbkey->data[0] = certDBEntryTypeSMimeProfile; + + return(SECSuccess); + +loser: + return(SECFailure); +} + +/* + * Decode a database SMIME record + */ +static SECStatus +DecodeDBSMimeEntry(certDBEntrySMime *entry, SECItem *dbentry, char *emailAddr) +{ + /* is record long enough for header? */ + if ( dbentry->len < DB_SMIME_ENTRY_HEADER_LEN ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + /* is database entry correct length? */ + entry->subjectName.len = ( ( dbentry->data[0] << 8 ) | dbentry->data[1] ); + entry->smimeOptions.len = ( ( dbentry->data[2] << 8 ) | dbentry->data[3] ); + entry->optionsDate.len = ( ( dbentry->data[4] << 8 ) | dbentry->data[5] ); + if (( entry->subjectName.len + entry->smimeOptions.len + + entry->optionsDate.len + DB_SMIME_ENTRY_HEADER_LEN ) != dbentry->len){ + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + /* copy the subject name */ + entry->subjectName.data = + (unsigned char *)PORT_ArenaAlloc(entry->common.arena, + entry->subjectName.len); + if ( entry->subjectName.data == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + PORT_Memcpy(entry->subjectName.data, + &dbentry->data[DB_SMIME_ENTRY_HEADER_LEN], + entry->subjectName.len); + + /* copy the smime options */ + if ( entry->smimeOptions.len ) { + entry->smimeOptions.data = + (unsigned char *)PORT_ArenaAlloc(entry->common.arena, + entry->smimeOptions.len); + if ( entry->smimeOptions.data == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + PORT_Memcpy(entry->smimeOptions.data, + &dbentry->data[DB_SMIME_ENTRY_HEADER_LEN + + entry->subjectName.len], + entry->smimeOptions.len); + } + if ( entry->optionsDate.len ) { + entry->optionsDate.data = + (unsigned char *)PORT_ArenaAlloc(entry->common.arena, + entry->optionsDate.len); + if ( entry->optionsDate.data == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + PORT_Memcpy(entry->optionsDate.data, + &dbentry->data[DB_SMIME_ENTRY_HEADER_LEN + + entry->subjectName.len + + entry->smimeOptions.len], + entry->optionsDate.len); + } + + /* both options and options date must either exist or not exist */ + if ( ( ( entry->optionsDate.len == 0 ) || + ( entry->smimeOptions.len == 0 ) ) && + entry->smimeOptions.len != entry->optionsDate.len ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + entry->emailAddr = (char *)PORT_Alloc(PORT_Strlen(emailAddr)+1); + if ( entry->emailAddr ) { + PORT_Strcpy(entry->emailAddr, emailAddr); + } + + return(SECSuccess); + +loser: + return(SECFailure); +} + +/* + * create a new SMIME entry + */ +static certDBEntrySMime * +NewDBSMimeEntry(char *emailAddr, SECItem *subjectName, SECItem *smimeOptions, + SECItem *optionsDate, unsigned int flags) +{ + PRArenaPool *arena = NULL; + certDBEntrySMime *entry; + int addrlen; + SECStatus rv; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + entry = (certDBEntrySMime *)PORT_ArenaAlloc(arena, + sizeof(certDBEntrySMime)); + if ( entry == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* init common fields */ + entry->common.arena = arena; + entry->common.type = certDBEntryTypeSMimeProfile; + entry->common.version = CERT_DB_FILE_VERSION; + entry->common.flags = flags; + + /* copy the email addr */ + addrlen = PORT_Strlen(emailAddr) + 1; + + entry->emailAddr = (char*)PORT_ArenaAlloc(arena, addrlen); + if ( entry->emailAddr == NULL ) { + goto loser; + } + + PORT_Memcpy(entry->emailAddr, emailAddr, addrlen); + + /* copy the subject name */ + rv = SECITEM_CopyItem(arena, &entry->subjectName, subjectName); + if ( rv != SECSuccess ) { + goto loser; + } + + /* copy the smime options */ + if ( smimeOptions ) { + rv = SECITEM_CopyItem(arena, &entry->smimeOptions, smimeOptions); + if ( rv != SECSuccess ) { + goto loser; + } + } else { + PORT_Assert(optionsDate == NULL); + entry->smimeOptions.data = NULL; + entry->smimeOptions.len = 0; + } + + /* copy the options date */ + if ( optionsDate ) { + rv = SECITEM_CopyItem(arena, &entry->optionsDate, optionsDate); + if ( rv != SECSuccess ) { + goto loser; + } + } else { + PORT_Assert(smimeOptions == NULL); + entry->optionsDate.data = NULL; + entry->optionsDate.len = 0; + } + + return(entry); +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(NULL); +} + +/* + * delete a SMIME entry + */ +static SECStatus +DeleteDBSMimeEntry(CERTCertDBHandle *handle, char *emailAddr) +{ + PRArenaPool *arena = NULL; + SECStatus rv; + SECItem dbkey; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + rv = EncodeDBSMimeKey(emailAddr, arena, &dbkey); + if ( rv != SECSuccess ) { + goto loser; + } + + rv = DeleteDBEntry(handle, certDBEntryTypeSMimeProfile, &dbkey); + if ( rv == SECFailure ) { + goto loser; + } + + PORT_FreeArena(arena, PR_FALSE); + return(SECSuccess); + +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(SECFailure); +} + +/* + * Read a SMIME entry + */ +static certDBEntrySMime * +ReadDBSMimeEntry(CERTCertDBHandle *handle, char *emailAddr) +{ + PRArenaPool *arena = NULL; + PRArenaPool *tmparena = NULL; + certDBEntrySMime *entry; + SECItem dbkey; + SECItem dbentry; + SECStatus rv; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( tmparena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + entry = (certDBEntrySMime *)PORT_ArenaAlloc(arena, + sizeof(certDBEntrySMime)); + if ( entry == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + entry->common.arena = arena; + entry->common.type = certDBEntryTypeSMimeProfile; + + rv = EncodeDBSMimeKey(emailAddr, tmparena, &dbkey); + if ( rv != SECSuccess ) { + goto loser; + } + + rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena); + if ( rv == SECFailure ) { + goto loser; + } + + /* is record long enough for header? */ + if ( dbentry.len < DB_SMIME_ENTRY_HEADER_LEN ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + rv = DecodeDBSMimeEntry(entry, &dbentry, emailAddr); + if ( rv != SECSuccess ) { + goto loser; + } + + PORT_FreeArena(tmparena, PR_FALSE); + return(entry); + +loser: + if ( tmparena ) { + PORT_FreeArena(tmparena, PR_FALSE); + } + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(NULL); +} + +/* + * Encode a SMIME entry into byte stream suitable for + * the database + */ +static SECStatus +WriteDBSMimeEntry(CERTCertDBHandle *handle, certDBEntrySMime *entry) +{ + SECItem dbitem, dbkey; + PRArenaPool *tmparena = NULL; + SECStatus rv; + + tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( tmparena == NULL ) { + goto loser; + } + + rv = EncodeDBSMimeEntry(entry, tmparena, &dbitem); + if ( rv != SECSuccess ) { + goto loser; + } + + rv = EncodeDBSMimeKey(entry->emailAddr, tmparena, &dbkey); + if ( rv != SECSuccess ) { + goto loser; + } + + /* now write it to the database */ + rv = WriteDBEntry(handle, &entry->common, &dbkey, &dbitem); + if ( rv != SECSuccess ) { + goto loser; + } + + PORT_FreeArena(tmparena, PR_FALSE); + return(SECSuccess); + +loser: + if ( tmparena ) { + PORT_FreeArena(tmparena, PR_FALSE); + } + return(SECFailure); + +} + +/* + * Encode a database subject record + */ +static SECStatus +EncodeDBSubjectEntry(certDBEntrySubject *entry, PRArenaPool *arena, + SECItem *dbitem) +{ + unsigned char *buf; + int len; + unsigned int ncerts; + unsigned int i; + unsigned char *tmpbuf; + unsigned int nnlen = 0; + unsigned int eaddrlen = 0; + int keyidoff; + SECItem *certKeys; + SECItem *keyIDs; + + if ( entry->nickname ) { + nnlen = PORT_Strlen(entry->nickname) + 1; + } + if ( entry->emailAddr ) { + eaddrlen = PORT_Strlen(entry->emailAddr) + 1; + } + + ncerts = entry->ncerts; + + /* compute the length of the entry */ + keyidoff = DB_SUBJECT_ENTRY_HEADER_LEN + nnlen + eaddrlen; + len = keyidoff + 4 * ncerts; + for ( i = 0; i < ncerts; i++ ) { + len += entry->certKeys[i].len; + len += entry->keyIDs[i].len; + } + + /* allocate space for encoded database record, including space + * for low level header + */ + dbitem->len = len + SEC_DB_ENTRY_HEADER_LEN; + + dbitem->data = (unsigned char *)PORT_ArenaAlloc(arena, dbitem->len); + if ( dbitem->data == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* fill in database record */ + buf = &dbitem->data[SEC_DB_ENTRY_HEADER_LEN]; + + buf[0] = ( ncerts >> 8 ) & 0xff; + buf[1] = ncerts & 0xff; + buf[2] = ( nnlen >> 8 ) & 0xff; + buf[3] = nnlen & 0xff; + buf[4] = ( eaddrlen >> 8 ) & 0xff; + buf[5] = eaddrlen & 0xff; + + PORT_Memcpy(&buf[DB_SUBJECT_ENTRY_HEADER_LEN], entry->nickname, nnlen); + PORT_Memcpy(&buf[DB_SUBJECT_ENTRY_HEADER_LEN+nnlen], entry->emailAddr, + eaddrlen); + + for ( i = 0; i < ncerts; i++ ) { + + certKeys = entry->certKeys; + keyIDs = entry->keyIDs; + + buf[keyidoff+i*2] = ( certKeys[i].len >> 8 ) & 0xff; + buf[keyidoff+1+i*2] = certKeys[i].len & 0xff; + buf[keyidoff+ncerts*2+i*2] = ( keyIDs[i].len >> 8 ) & 0xff; + buf[keyidoff+1+ncerts*2+i*2] = keyIDs[i].len & 0xff; + } + + /* temp pointer used to stuff certkeys and keyids into the buffer */ + tmpbuf = &buf[keyidoff+ncerts*4]; + + for ( i = 0; i < ncerts; i++ ) { + certKeys = entry->certKeys; + PORT_Memcpy(tmpbuf, certKeys[i].data, certKeys[i].len); + tmpbuf = tmpbuf + certKeys[i].len; + } + + for ( i = 0; i < ncerts; i++ ) { + keyIDs = entry->keyIDs; + PORT_Memcpy(tmpbuf, keyIDs[i].data, keyIDs[i].len); + tmpbuf = tmpbuf + keyIDs[i].len; + } + + PORT_Assert(tmpbuf == &buf[len]); + + return(SECSuccess); + +loser: + return(SECFailure); +} + +/* + * Encode a database key for a subject record + */ +static SECStatus +EncodeDBSubjectKey(SECItem *derSubject, PRArenaPool *arena, + SECItem *dbkey) +{ + dbkey->len = derSubject->len + SEC_DB_KEY_HEADER_LEN; + dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey->len); + if ( dbkey->data == NULL ) { + goto loser; + } + PORT_Memcpy(&dbkey->data[SEC_DB_KEY_HEADER_LEN], derSubject->data, + derSubject->len); + dbkey->data[0] = certDBEntryTypeSubject; + + return(SECSuccess); + +loser: + return(SECFailure); +} + +static SECStatus +DecodeDBSubjectEntry(certDBEntrySubject *entry, SECItem *dbentry, + SECItem *derSubject) +{ + unsigned int ncerts; + PRArenaPool *arena; + unsigned int len, itemlen; + unsigned char *tmpbuf; + unsigned int i; + SECStatus rv; + unsigned int keyidoff; + unsigned int nnlen, eaddrlen; + + arena = entry->common.arena; + + rv = SECITEM_CopyItem(arena, &entry->derSubject, derSubject); + if ( rv != SECSuccess ) { + goto loser; + } + + /* is record long enough for header? */ + if ( dbentry->len < DB_SUBJECT_ENTRY_HEADER_LEN ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + entry->ncerts = ncerts = ( ( dbentry->data[0] << 8 ) | dbentry->data[1] ); + nnlen = ( ( dbentry->data[2] << 8 ) | dbentry->data[3] ); + eaddrlen = ( ( dbentry->data[4] << 8 ) | dbentry->data[5] ); + if ( dbentry->len < ( ncerts * 4 + DB_SUBJECT_ENTRY_HEADER_LEN + + nnlen + eaddrlen) ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + entry->certKeys = (SECItem *)PORT_ArenaAlloc(arena, + sizeof(SECItem) * ncerts); + entry->keyIDs = (SECItem *)PORT_ArenaAlloc(arena, + sizeof(SECItem) * ncerts); + + if ( ( entry->certKeys == NULL ) || ( entry->keyIDs == NULL ) ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + if ( nnlen > 1 ) { /* null terminator is stored */ + entry->nickname = (char *)PORT_ArenaAlloc(arena, nnlen); + if ( entry->nickname == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + PORT_Memcpy(entry->nickname, + &dbentry->data[DB_SUBJECT_ENTRY_HEADER_LEN], + nnlen); + } else { + entry->nickname = NULL; + } + + if ( eaddrlen > 1 ) { /* null terminator is stored */ + entry->emailAddr = (char *)PORT_ArenaAlloc(arena, eaddrlen); + if ( entry->emailAddr == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + PORT_Memcpy(entry->emailAddr, + &dbentry->data[DB_SUBJECT_ENTRY_HEADER_LEN+nnlen], + eaddrlen); + } else { + entry->emailAddr = NULL; + } + + /* collect the lengths of the certKeys and keyIDs, and total the + * overall length. + */ + keyidoff = DB_SUBJECT_ENTRY_HEADER_LEN + nnlen + eaddrlen; + len = keyidoff + 4 * ncerts; + + tmpbuf = &dbentry->data[0]; + + for ( i = 0; i < ncerts; i++ ) { + + itemlen = ( tmpbuf[keyidoff + 2*i] << 8 ) | tmpbuf[keyidoff + 1 + 2*i] ; + len += itemlen; + entry->certKeys[i].len = itemlen; + + itemlen = ( tmpbuf[keyidoff + 2*ncerts + 2*i] << 8 ) | + tmpbuf[keyidoff + 1 + 2*ncerts + 2*i] ; + len += itemlen; + entry->keyIDs[i].len = itemlen; + } + + /* is database entry correct length? */ + if ( len != dbentry->len ){ + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + tmpbuf = &tmpbuf[keyidoff + 4*ncerts]; + for ( i = 0; i < ncerts; i++ ) { + entry->certKeys[i].data = + (unsigned char *)PORT_ArenaAlloc(arena, entry->certKeys[i].len); + if ( entry->certKeys[i].data == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + PORT_Memcpy(entry->certKeys[i].data, tmpbuf, entry->certKeys[i].len); + tmpbuf = &tmpbuf[entry->certKeys[i].len]; + } + + for ( i = 0; i < ncerts; i++ ) { + entry->keyIDs[i].data = + (unsigned char *)PORT_ArenaAlloc(arena, entry->keyIDs[i].len); + if ( entry->keyIDs[i].data == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + PORT_Memcpy(entry->keyIDs[i].data, tmpbuf, entry->keyIDs[i].len); + tmpbuf = &tmpbuf[entry->keyIDs[i].len]; + } + + PORT_Assert(tmpbuf == &dbentry->data[dbentry->len]); + + return(SECSuccess); + +loser: + return(SECFailure); +} + +/* + * create a new subject entry with a single cert + */ +static certDBEntrySubject * +NewDBSubjectEntry(SECItem *derSubject, SECItem *certKey, + SECItem *keyID, char *nickname, char *emailAddr, + unsigned int flags) +{ + PRArenaPool *arena = NULL; + certDBEntrySubject *entry; + SECStatus rv; + unsigned int nnlen; + unsigned int eaddrlen; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + entry = (certDBEntrySubject *)PORT_ArenaAlloc(arena, + sizeof(certDBEntrySubject)); + if ( entry == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* init common fields */ + entry->common.arena = arena; + entry->common.type = certDBEntryTypeSubject; + entry->common.version = CERT_DB_FILE_VERSION; + entry->common.flags = flags; + + /* copy the subject */ + rv = SECITEM_CopyItem(arena, &entry->derSubject, derSubject); + if ( rv != SECSuccess ) { + goto loser; + } + + entry->ncerts = 1; + /* copy nickname */ + if ( nickname && ( *nickname != '\0' ) ) { + nnlen = PORT_Strlen(nickname) + 1; + entry->nickname = (char *)PORT_ArenaAlloc(arena, nnlen); + if ( entry->nickname == NULL ) { + goto loser; + } + + PORT_Memcpy(entry->nickname, nickname, nnlen); + } else { + entry->nickname = NULL; + } + + /* copy email addr */ + if ( emailAddr && ( *emailAddr != '\0' ) ) { + emailAddr = CERT_FixupEmailAddr(emailAddr); + if ( emailAddr == NULL ) { + entry->emailAddr = NULL; + goto loser; + } + + eaddrlen = PORT_Strlen(emailAddr) + 1; + entry->emailAddr = (char *)PORT_ArenaAlloc(arena, eaddrlen); + if ( entry->emailAddr == NULL ) { + PORT_Free(emailAddr); + goto loser; + } + + PORT_Memcpy(entry->emailAddr, emailAddr, eaddrlen); + PORT_Free(emailAddr); + } else { + entry->emailAddr = NULL; + } + + /* allocate space for certKeys and keyIDs */ + entry->certKeys = (SECItem *)PORT_ArenaAlloc(arena, sizeof(SECItem)); + entry->keyIDs = (SECItem *)PORT_ArenaAlloc(arena, sizeof(SECItem)); + if ( ( entry->certKeys == NULL ) || ( entry->keyIDs == NULL ) ) { + goto loser; + } + + /* copy the certKey and keyID */ + rv = SECITEM_CopyItem(arena, &entry->certKeys[0], certKey); + if ( rv != SECSuccess ) { + goto loser; + } + rv = SECITEM_CopyItem(arena, &entry->keyIDs[0], keyID); + if ( rv != SECSuccess ) { + goto loser; + } + + return(entry); +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(NULL); +} + +/* + * delete a subject entry + */ +static SECStatus +DeleteDBSubjectEntry(CERTCertDBHandle *handle, SECItem *derSubject) +{ + SECItem dbkey; + PRArenaPool *arena = NULL; + SECStatus rv; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + rv = EncodeDBSubjectKey(derSubject, arena, &dbkey); + if ( rv != SECSuccess ) { + goto loser; + } + + rv = DeleteDBEntry(handle, certDBEntryTypeSubject, &dbkey); + if ( rv == SECFailure ) { + goto loser; + } + + PORT_FreeArena(arena, PR_FALSE); + return(SECSuccess); + +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(SECFailure); +} + +/* + * Read the subject entry + */ +static certDBEntrySubject * +ReadDBSubjectEntry(CERTCertDBHandle *handle, SECItem *derSubject) +{ + PRArenaPool *arena = NULL; + PRArenaPool *tmparena = NULL; + certDBEntrySubject *entry; + SECItem dbkey; + SECItem dbentry; + SECStatus rv; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( tmparena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + entry = (certDBEntrySubject *)PORT_ArenaAlloc(arena, + sizeof(certDBEntrySubject)); + if ( entry == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + entry->common.arena = arena; + entry->common.type = certDBEntryTypeSubject; + + rv = EncodeDBSubjectKey(derSubject, tmparena, &dbkey); + if ( rv != SECSuccess ) { + goto loser; + } + + rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena); + if ( rv == SECFailure ) { + goto loser; + } + + rv = DecodeDBSubjectEntry(entry, &dbentry, derSubject); + if ( rv == SECFailure ) { + goto loser; + } + + PORT_FreeArena(tmparena, PR_FALSE); + return(entry); + +loser: + if ( tmparena ) { + PORT_FreeArena(tmparena, PR_FALSE); + } + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(NULL); +} + +/* + * Encode a subject name entry into byte stream suitable for + * the database + */ +static SECStatus +WriteDBSubjectEntry(CERTCertDBHandle *handle, certDBEntrySubject *entry) +{ + SECItem dbitem, dbkey; + PRArenaPool *tmparena = NULL; + SECStatus rv; + + tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( tmparena == NULL ) { + goto loser; + } + + rv = EncodeDBSubjectEntry(entry, tmparena, &dbitem); + if ( rv != SECSuccess ) { + goto loser; + } + + rv = EncodeDBSubjectKey(&entry->derSubject, tmparena, &dbkey); + if ( rv != SECSuccess ) { + goto loser; + } + + /* now write it to the database */ + rv = WriteDBEntry(handle, &entry->common, &dbkey, &dbitem); + if ( rv != SECSuccess ) { + goto loser; + } + + PORT_FreeArena(tmparena, PR_FALSE); + return(SECSuccess); + +loser: + if ( tmparena ) { + PORT_FreeArena(tmparena, PR_FALSE); + } + return(SECFailure); + +} + +static SECStatus +UpdateSubjectWithEmailAddr(CERTCertificate *cert, char *emailAddr) +{ + CERTSubjectList *subjectList; + PRBool save = PR_FALSE, delold = PR_FALSE; + certDBEntrySubject *entry; + SECStatus rv; + + emailAddr = CERT_FixupEmailAddr(emailAddr); + if ( emailAddr == NULL ) { + return(SECFailure); + } + + subjectList = cert->subjectList; + PORT_Assert(subjectList != NULL); + + if ( subjectList->emailAddr ) { + if ( PORT_Strcmp(subjectList->emailAddr, emailAddr) != 0 ) { + save = PR_TRUE; + delold = PR_TRUE; + } + } else { + save = PR_TRUE; + } + + if ( delold ) { + /* delete the old smime entry, because this cert now has a new + * smime entry pointing to it + */ + PORT_Assert(save); + PORT_Assert(subjectList->emailAddr != NULL); + DeleteDBSMimeEntry(cert->dbhandle, subjectList->emailAddr); + } + + if ( save ) { + unsigned int len; + + entry = subjectList->entry; + + PORT_Assert(entry != NULL); + len = PORT_Strlen(emailAddr) + 1; + entry->emailAddr = (char *)PORT_ArenaAlloc(entry->common.arena, len); + if ( entry->emailAddr == NULL ) { + goto loser; + } + PORT_Memcpy(entry->emailAddr, emailAddr, len); + + /* delete the subject entry */ + DeleteDBSubjectEntry(cert->dbhandle, &cert->derSubject); + + /* write the new one */ + rv = WriteDBSubjectEntry(cert->dbhandle, entry); + if ( rv != SECSuccess ) { + goto loser; + } + } + + PORT_Free(emailAddr); + return(SECSuccess); + +loser: + PORT_Free(emailAddr); + return(SECFailure); +} + +/* + * writes a nickname to an existing subject entry that does not currently + * have one + */ +static SECStatus +AddNicknameToSubject(CERTCertificate *cert, char *nickname) +{ + CERTSubjectList *subjectList; + certDBEntrySubject *entry; + SECStatus rv; + + if ( nickname == NULL ) { + return(SECFailure); + } + + subjectList = cert->subjectList; + PORT_Assert(subjectList != NULL); + if ( subjectList == NULL ) { + goto loser; + } + + entry = subjectList->entry; + PORT_Assert(entry != NULL); + if ( entry == NULL ) { + goto loser; + } + + PORT_Assert(entry->nickname == NULL); + if ( entry->nickname != NULL ) { + goto loser; + } + + entry->nickname = (nickname) ? PORT_ArenaStrdup(entry->common.arena, nickname) : NULL; + + if ( entry->nickname == NULL ) { + goto loser; + } + + /* delete the subject entry */ + DeleteDBSubjectEntry(cert->dbhandle, &cert->derSubject); + + /* write the new one */ + rv = WriteDBSubjectEntry(cert->dbhandle, entry); + if ( rv != SECSuccess ) { + goto loser; + } + + return(SECSuccess); + +loser: + return(SECFailure); +} + +/* + * create a new version entry + */ +static certDBEntryVersion * +NewDBVersionEntry(unsigned int flags) +{ + PRArenaPool *arena = NULL; + certDBEntryVersion *entry; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + entry = (certDBEntryVersion *)PORT_ArenaAlloc(arena, + sizeof(certDBEntryVersion)); + if ( entry == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + entry->common.arena = arena; + entry->common.type = certDBEntryTypeVersion; + entry->common.version = CERT_DB_FILE_VERSION; + entry->common.flags = flags; + + return(entry); +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(NULL); +} + +/* + * Read the version entry + */ +static certDBEntryVersion * +ReadDBVersionEntry(CERTCertDBHandle *handle) +{ + PRArenaPool *arena = NULL; + PRArenaPool *tmparena = NULL; + certDBEntryVersion *entry; + SECItem dbkey; + SECItem dbentry; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( tmparena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + entry = (certDBEntryVersion *)PORT_ArenaAlloc(arena, + sizeof(certDBEntryVersion)); + if ( entry == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + entry->common.arena = arena; + entry->common.type = certDBEntryTypeVersion; + + /* now get the database key and format it */ + dbkey.len = SEC_DB_VERSION_KEY_LEN + SEC_DB_KEY_HEADER_LEN; + dbkey.data = (unsigned char *)PORT_ArenaAlloc(tmparena, dbkey.len); + if ( dbkey.data == NULL ) { + goto loser; + } + PORT_Memcpy(&dbkey.data[SEC_DB_KEY_HEADER_LEN], SEC_DB_VERSION_KEY, + SEC_DB_VERSION_KEY_LEN); + + ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena); + + PORT_FreeArena(tmparena, PR_FALSE); + return(entry); + +loser: + if ( tmparena ) { + PORT_FreeArena(tmparena, PR_FALSE); + } + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(NULL); +} + + +/* + * Encode a version entry into byte stream suitable for + * the database + */ +static SECStatus +WriteDBVersionEntry(CERTCertDBHandle *handle, certDBEntryVersion *entry) +{ + SECItem dbitem, dbkey; + PRArenaPool *tmparena = NULL; + SECStatus rv; + + tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( tmparena == NULL ) { + goto loser; + } + + /* allocate space for encoded database record, including space + * for low level header + */ + dbitem.len = SEC_DB_ENTRY_HEADER_LEN; + + dbitem.data = (unsigned char *)PORT_ArenaAlloc(tmparena, dbitem.len); + if ( dbitem.data == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* now get the database key and format it */ + dbkey.len = SEC_DB_VERSION_KEY_LEN + SEC_DB_KEY_HEADER_LEN; + dbkey.data = (unsigned char *)PORT_ArenaAlloc(tmparena, dbkey.len); + if ( dbkey.data == NULL ) { + goto loser; + } + PORT_Memcpy(&dbkey.data[SEC_DB_KEY_HEADER_LEN], SEC_DB_VERSION_KEY, + SEC_DB_VERSION_KEY_LEN); + + /* now write it to the database */ + rv = WriteDBEntry(handle, &entry->common, &dbkey, &dbitem); + if ( rv != SECSuccess ) { + goto loser; + } + + PORT_FreeArena(tmparena, PR_FALSE); + return(SECSuccess); + +loser: + if ( tmparena ) { + PORT_FreeArena(tmparena, PR_FALSE); + } + return(SECFailure); +} + +/* + * create a new version entry + */ +static certDBEntryContentVersion * +NewDBContentVersionEntry(unsigned int flags) +{ + PRArenaPool *arena = NULL; + certDBEntryContentVersion *entry; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + entry = (certDBEntryContentVersion *) + PORT_ArenaAlloc(arena, sizeof(certDBEntryContentVersion)); + if ( entry == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + entry->common.arena = arena; + entry->common.type = certDBEntryTypeContentVersion; + entry->common.version = CERT_DB_FILE_VERSION; + entry->common.flags = flags; + + entry->contentVersion = CERT_DB_CONTENT_VERSION; + + return(entry); +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(NULL); +} + +/* + * Read the version entry + */ +static certDBEntryContentVersion * +ReadDBContentVersionEntry(CERTCertDBHandle *handle) +{ + PRArenaPool *arena = NULL; + PRArenaPool *tmparena = NULL; + certDBEntryContentVersion *entry; + SECItem dbkey; + SECItem dbentry; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( tmparena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + entry = (certDBEntryContentVersion *) + PORT_ArenaAlloc(arena, sizeof(certDBEntryContentVersion)); + if ( entry == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + entry->common.arena = arena; + entry->common.type = certDBEntryTypeContentVersion; + + /* now get the database key and format it */ + dbkey.len = SEC_DB_CONTENT_VERSION_KEY_LEN + SEC_DB_KEY_HEADER_LEN; + dbkey.data = (unsigned char *)PORT_ArenaAlloc(tmparena, dbkey.len); + if ( dbkey.data == NULL ) { + goto loser; + } + PORT_Memcpy(&dbkey.data[SEC_DB_KEY_HEADER_LEN], SEC_DB_CONTENT_VERSION_KEY, + SEC_DB_CONTENT_VERSION_KEY_LEN); + + dbentry.len = 0; + dbentry.data = NULL; + + ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena); + + if ( dbentry.len != 1 ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + entry->contentVersion = dbentry.data[0]; + + PORT_FreeArena(tmparena, PR_FALSE); + return(entry); + +loser: + if ( tmparena ) { + PORT_FreeArena(tmparena, PR_FALSE); + } + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(NULL); +} + +/* + * Encode a version entry into byte stream suitable for + * the database + */ +static SECStatus +WriteDBContentVersionEntry(CERTCertDBHandle *handle, + certDBEntryContentVersion *entry) +{ + SECItem dbitem, dbkey; + PRArenaPool *tmparena = NULL; + SECStatus rv; + + tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( tmparena == NULL ) { + goto loser; + } + + /* allocate space for encoded database record, including space + * for low level header + */ + dbitem.len = SEC_DB_ENTRY_HEADER_LEN + 1; + + dbitem.data = (unsigned char *)PORT_ArenaAlloc(tmparena, dbitem.len); + if ( dbitem.data == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + dbitem.data[SEC_DB_ENTRY_HEADER_LEN] = entry->contentVersion; + + /* now get the database key and format it */ + dbkey.len = SEC_DB_CONTENT_VERSION_KEY_LEN + SEC_DB_KEY_HEADER_LEN; + dbkey.data = (unsigned char *)PORT_ArenaAlloc(tmparena, dbkey.len); + if ( dbkey.data == NULL ) { + goto loser; + } + PORT_Memcpy(&dbkey.data[SEC_DB_KEY_HEADER_LEN], SEC_DB_CONTENT_VERSION_KEY, + SEC_DB_CONTENT_VERSION_KEY_LEN); + + /* now write it to the database */ + rv = WriteDBEntry(handle, &entry->common, &dbkey, &dbitem); + if ( rv != SECSuccess ) { + goto loser; + } + + PORT_FreeArena(tmparena, PR_FALSE); + return(SECSuccess); + +loser: + if ( tmparena ) { + PORT_FreeArena(tmparena, PR_FALSE); + } + return(SECFailure); +} + +/* + * delete a content version entry + */ +static SECStatus +DeleteDBContentVersionEntry(CERTCertDBHandle *handle) +{ + SECItem dbkey; + PRArenaPool *arena = NULL; + SECStatus rv; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + /* now get the database key and format it */ + dbkey.len = SEC_DB_CONTENT_VERSION_KEY_LEN + SEC_DB_KEY_HEADER_LEN; + dbkey.data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey.len); + if ( dbkey.data == NULL ) { + goto loser; + } + PORT_Memcpy(&dbkey.data[SEC_DB_KEY_HEADER_LEN], SEC_DB_CONTENT_VERSION_KEY, + SEC_DB_CONTENT_VERSION_KEY_LEN); + + rv = DeleteDBEntry(handle, certDBEntryTypeContentVersion, &dbkey); + if ( rv == SECFailure ) { + goto loser; + } + + PORT_FreeArena(arena, PR_FALSE); + return(SECSuccess); + +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(SECFailure); +} + +/* + * Routines and datastructures to manage the list of certificates for a + * particular subject name. + */ + +/* + * Create a new certificate subject list. If entry exists, then populate + * the list with the entries from the permanent database. + */ +static CERTSubjectList * +NewSubjectList(certDBEntrySubject *entry) +{ + PRArenaPool *permarena; + unsigned int i; + CERTSubjectList *subjectList; + CERTSubjectNode *node; + SECStatus rv; + + permarena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( permarena == NULL ) { + goto loser; + } + subjectList = (CERTSubjectList *)PORT_ArenaAlloc(permarena, + sizeof(CERTSubjectList)); + if ( subjectList == NULL ) { + goto loser; + } + + subjectList->arena = permarena; + subjectList->ncerts = 0; + subjectList->head = NULL; + subjectList->tail = NULL; + subjectList->entry = entry; + subjectList->emailAddr = NULL; + if ( entry ) { + + /* initialize the list with certs from database entry */ + for ( i = 0; i < entry->ncerts; i++ ) { + /* Init the node */ + node = (CERTSubjectNode *)PORT_ArenaAlloc(permarena, + sizeof(CERTSubjectNode)); + if ( node == NULL ) { + goto loser; + } + + /* copy certKey and keyID to node */ + rv = SECITEM_CopyItem(permarena, &node->certKey, + &entry->certKeys[i]); + if ( rv != SECSuccess ) { + goto loser; + } + rv = SECITEM_CopyItem(permarena, &node->keyID, + &entry->keyIDs[i]); + if ( rv != SECSuccess ) { + goto loser; + } + + /* the certs are already in order, so just add them + * to the tail. + */ + node->next = NULL; + if ( subjectList->tail == NULL ) { + /* first in list */ + subjectList->head = node; + subjectList->tail = node; + node->prev = NULL; + } else { + /* add to end of list */ + node->prev = subjectList->tail; + subjectList->tail = node; + node->prev->next = node; + } + subjectList->ncerts++; + } + } + + return(subjectList); + +loser: + PORT_FreeArena(permarena, PR_FALSE); + return(NULL); +} + +/* + * Find the Subject entry in the temp database. It it is not in the + * temp database, then get it from the perm DB. It its not there either, + * then create a new one. + */ +static CERTSubjectList * +FindSubjectList(CERTCertDBHandle *handle, SECItem *subject, PRBool create) +{ + PRArenaPool *arena = NULL; + SECItem keyitem; + SECStatus rv; + DBT namekey; + DBT tmpdata; + int ret; + CERTSubjectList *subjectList = NULL; + certDBEntrySubject *entry; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + rv = EncodeDBSubjectKey(subject, arena, &keyitem); + if ( rv != SECSuccess ) { + goto loser; + } + + namekey.data = keyitem.data; + namekey.size = keyitem.len; + + /* lookup in the temporary database */ + ret = certdb_Get(handle->tempCertDB, &namekey, &tmpdata, 0); + + /* error accessing the database */ + if ( ret < 0 ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + if ( ret == 0 ) { /* found in temp database */ + if ( tmpdata.size != sizeof(CERTCertificate *) ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + /* copy pointer out of database */ + PORT_Memcpy(&subjectList, tmpdata.data, tmpdata.size); + } else { /* not found in temporary database */ + entry = ReadDBSubjectEntry(handle, subject); + if ( entry || create ) { + /* decode or create new subject list */ + subjectList = NewSubjectList(entry); + + /* put it in the temp database */ + if ( subjectList ) { + tmpdata.data = (unsigned char *)(&subjectList); + tmpdata.size = sizeof(subjectList); + ret = certdb_Put(handle->tempCertDB, &namekey, + &tmpdata, R_NOOVERWRITE); + if ( ret ) { + goto loser; + } + } + } else { + PORT_SetError(SEC_ERROR_UNKNOWN_CERT); + goto loser; + } + } + + goto done; + +loser: + subjectList = NULL; + +done: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(subjectList); +} + +/* + * Add a temp cert to the temp subject list + */ +static SECStatus +AddTempCertToSubjectList(CERTCertificate *cert) +{ + CERTSubjectList *subjectList; + CERTSubjectNode *node, *newnode; + CERTCertificate *cmpcert; + PRBool newer; + SECStatus rv; + + PORT_Assert(cert->isperm == PR_FALSE); + PORT_Assert(cert->subjectList == NULL); + + subjectList = FindSubjectList(cert->dbhandle, &cert->derSubject, PR_TRUE); + + if ( subjectList == NULL ) { + goto loser; + } + + newnode = (CERTSubjectNode*)PORT_ArenaAlloc(subjectList->arena, + sizeof(CERTSubjectNode)); + /* copy certKey and keyID to node */ + rv = SECITEM_CopyItem(subjectList->arena, &newnode->certKey, + &cert->certKey); + if ( rv != SECSuccess ) { + goto loser; + } + rv = SECITEM_CopyItem(subjectList->arena, &newnode->keyID, + &cert->subjectKeyID); + if ( rv != SECSuccess ) { + goto loser; + } + + node = subjectList->head; + + if ( node ) { + /* list is not empty */ + while ( node ) { + cmpcert = CERT_FindCertByKeyNoLocking(cert->dbhandle, + &node->certKey); + if ( cmpcert ) { + + newer = CERT_IsNewer(cert, cmpcert); + CERT_DestroyCertificateNoLocking(cmpcert); + if ( newer ) { + /* insert before this cert */ + newnode->next = node; + newnode->prev = node->prev; + if ( newnode->prev ) { + newnode->prev->next = newnode; + } else { + /* at the head of the list */ + subjectList->head = newnode; + } + node->prev = newnode; + goto done; + } + } + node = node->next; + } + /* if we get here, we add the node to the end of the list */ + newnode->prev = subjectList->tail; + newnode->next = NULL; + subjectList->tail->next = newnode; + subjectList->tail = newnode; + } else { + /* this is a new/empty list */ + newnode->next = NULL; + newnode->prev = NULL; + subjectList->head = newnode; + subjectList->tail = newnode; + } + +done: + subjectList->ncerts++; + cert->subjectList = subjectList; + return(SECSuccess); + +loser: + return(SECFailure); +} + +/* + * Find the node in a subjectList that belongs a cert + */ +static CERTSubjectNode * +FindCertSubjectNode(CERTCertificate *cert) +{ + CERTSubjectList *subjectList; + CERTSubjectNode *node = NULL; + + PORT_Assert(cert->subjectList); + + subjectList = cert->subjectList; + + if ( subjectList ) { + node = subjectList->head; + } + + while ( node ) { + if ( SECITEM_CompareItem(&node->certKey, &cert->certKey) == SECEqual ){ + return(node); + break; + } + + node = node->next; + } + + return(NULL); +} + +/* + * Remove a temp cert from the temp subject list + */ +static SECStatus +RemoveTempCertFromSubjectList(CERTCertificate *cert) +{ + CERTSubjectList *subjectList; + CERTSubjectNode *node; + SECItem keyitem; + DBT namekey; + SECStatus rv; + int ret; + CERTCertDBHandle *handle; + + PORT_Assert(cert->subjectList); + + /* don't remove perm certs */ + if ( cert->isperm ) { + return(SECSuccess); + } + + subjectList = cert->subjectList; + + node = FindCertSubjectNode(cert); + + if ( node ) { + /* found it, unlink it */ + if ( node->next ) { + node->next->prev = node->prev; + } else { + /* removing from tail of list */ + subjectList->tail = node->prev; + } + if ( node->prev ) { + node->prev->next = node->next; + } else { + /* removing from head of list */ + subjectList->head = node->next; + } + + subjectList->ncerts--; + + /* dont need to free the node, because it is from subjectList + * arena. + */ + + /* remove reference from cert */ + cert->subjectList = NULL; + + /* if the list is now empty, remove the list from the db and free it */ + if ( subjectList->head == NULL ) { + PORT_Assert(subjectList->ncerts == 0); + rv = EncodeDBSubjectKey(&cert->derSubject, subjectList->arena, + &keyitem); + if ( rv == SECSuccess ) { + namekey.data = keyitem.data; + namekey.size = keyitem.len; + + handle = cert->dbhandle; + + ret = certdb_Del(handle->tempCertDB, &namekey, 0); + /* keep going if it fails */ + + if ( cert->dbnickname ) { + rv = SEC_DeleteTempNickname(handle, cert->dbnickname); + } else if ( cert->nickname ) { + rv = SEC_DeleteTempNickname(handle, cert->nickname); + } + + /* keep going if it fails */ + } + + PORT_FreeArena(subjectList->arena, PR_FALSE); + } + } + + PORT_Assert(cert->subjectList == NULL); + + if ( cert->subjectList != NULL ) { + return(SECFailure); + } + + return(SECSuccess); +} + +/* + * cert is no longer a perm cert, but will remain a temp cert + */ +static SECStatus +RemovePermSubjectNode(CERTCertificate *cert) +{ + CERTSubjectList *subjectList; + certDBEntrySubject *entry; + unsigned int i; + SECStatus rv; + + PORT_Assert(cert->isperm); + if ( !cert->isperm ) { + return(SECFailure); + } + + subjectList = cert->subjectList; + PORT_Assert(subjectList); + if ( subjectList == NULL ) { + return(SECFailure); + } + entry = subjectList->entry; + PORT_Assert(entry); + if ( entry == NULL ) { + return(SECFailure); + } + + PORT_Assert(entry->ncerts); + rv = SECFailure; + + if ( entry->ncerts > 1 ) { + for ( i = 0; i < entry->ncerts; i++ ) { + if ( SECITEM_CompareItem(&entry->certKeys[i], &cert->certKey) == + SECEqual ) { + /* copy rest of list forward one entry */ + for ( i = i + 1; i < entry->ncerts; i++ ) { + entry->certKeys[i-1] = entry->certKeys[i]; + entry->keyIDs[i-1] = entry->keyIDs[i]; + } + entry->ncerts--; + DeleteDBSubjectEntry(cert->dbhandle, &cert->derSubject); + rv = WriteDBSubjectEntry(cert->dbhandle, entry); + break; + } + } + } else { + /* no entries left, delete the perm entry in the DB */ + if ( subjectList->entry->emailAddr ) { + /* if the subject had an email record, then delete it too */ + DeleteDBSMimeEntry(cert->dbhandle, subjectList->entry->emailAddr); + } + + DestroyDBEntry((certDBEntry *)subjectList->entry); + subjectList->entry = NULL; + DeleteDBSubjectEntry(cert->dbhandle, &cert->derSubject); + } + + return(rv); +} + +/* + * add a cert to the perm subject list + */ +static SECStatus +AddPermSubjectNode(CERTCertificate *cert, char *nickname) +{ + CERTSubjectList *subjectList; + certDBEntrySubject *entry; + SECItem *newCertKeys, *newKeyIDs; + int i; + SECStatus rv; + CERTCertificate *cmpcert; + unsigned int nnlen; + int ncerts; + + subjectList = cert->subjectList; + + PORT_Assert(subjectList); + if ( subjectList == NULL ) { + return(SECFailure); + } + + entry = subjectList->entry; + + if ( entry ) { + ncerts = entry->ncerts; + + if ( nickname && entry->nickname ) { + /* nicknames must be the same */ + PORT_Assert(PORT_Strcmp(nickname, entry->nickname) == 0); + } + + if ( ( entry->nickname == NULL ) && ( nickname != NULL ) ) { + /* copy nickname into the entry */ + nnlen = PORT_Strlen(nickname) + 1; + entry->nickname = (char *)PORT_ArenaAlloc(entry->common.arena, + nnlen); + if ( entry->nickname == NULL ) { + return(SECFailure); + } + PORT_Memcpy(entry->nickname, nickname, nnlen); + } + + /* a DB entry already exists, so add this cert */ + newCertKeys = (SECItem *)PORT_ArenaAlloc(entry->common.arena, + sizeof(SECItem) * + ( ncerts + 1 ) ); + newKeyIDs = (SECItem *)PORT_ArenaAlloc(entry->common.arena, + sizeof(SECItem) * + ( ncerts + 1 ) ); + + if ( ( newCertKeys == NULL ) || ( newKeyIDs == NULL ) ) { + return(SECFailure); + } + + for ( i = 0; i < ncerts; i++ ) { + cmpcert = CERT_FindCertByKeyNoLocking(cert->dbhandle, + &entry->certKeys[i]); + PORT_Assert(cmpcert); + + if ( CERT_IsNewer(cert, cmpcert) ) { + /* insert before cmpcert */ + rv = SECITEM_CopyItem(entry->common.arena, &newCertKeys[i], + &cert->certKey); + if ( rv != SECSuccess ) { + return(SECFailure); + } + rv = SECITEM_CopyItem(entry->common.arena, &newKeyIDs[i], + &cert->subjectKeyID); + if ( rv != SECSuccess ) { + return(SECFailure); + } + /* copy the rest of the entry */ + for ( ; i < ncerts; i++ ) { + newCertKeys[i+1] = entry->certKeys[i]; + newKeyIDs[i+1] = entry->keyIDs[i]; + } + + /* update certKeys and keyIDs */ + entry->certKeys = newCertKeys; + entry->keyIDs = newKeyIDs; + + /* increment count */ + entry->ncerts++; + break; + } + /* copy this cert entry */ + newCertKeys[i] = entry->certKeys[i]; + newKeyIDs[i] = entry->keyIDs[i]; + } + + if ( entry->ncerts == ncerts ) { + /* insert new one at end */ + rv = SECITEM_CopyItem(entry->common.arena, &newCertKeys[ncerts], + &cert->certKey); + if ( rv != SECSuccess ) { + return(SECFailure); + } + rv = SECITEM_CopyItem(entry->common.arena, &newKeyIDs[ncerts], + &cert->subjectKeyID); + if ( rv != SECSuccess ) { + return(SECFailure); + } + + /* update certKeys and keyIDs */ + entry->certKeys = newCertKeys; + entry->keyIDs = newKeyIDs; + + /* increment count */ + entry->ncerts++; + } + } else { + /* need to make a new DB entry */ + entry = NewDBSubjectEntry(&cert->derSubject, &cert->certKey, + &cert->subjectKeyID, nickname, + NULL, 0); + cert->subjectList->entry = entry; + } + if ( entry ) { + DeleteDBSubjectEntry(cert->dbhandle, &cert->derSubject); + rv = WriteDBSubjectEntry(cert->dbhandle, entry); + } else { + rv = SECFailure; + } + + return(rv); +} + + + +SECStatus +CERT_TraversePermCertsForSubject(CERTCertDBHandle *handle, SECItem *derSubject, + CERTCertCallback cb, void *cbarg) +{ + certDBEntrySubject *entry; + int i; + CERTCertificate *cert; + SECStatus rv = SECSuccess; + + entry = ReadDBSubjectEntry(handle, derSubject); + + if ( entry == NULL ) { + return(SECFailure); + } + + for( i = 0; i < entry->ncerts; i++ ) { + cert = CERT_FindCertByKey(handle, &entry->certKeys[i]); + rv = (* cb)(cert, cbarg); + CERT_DestroyCertificate(cert); + if ( rv == SECFailure ) { + break; + } + } + + DestroyDBEntry((certDBEntry *)entry); + + return(rv); +} + +int +CERT_NumPermCertsForSubject(CERTCertDBHandle *handle, SECItem *derSubject) +{ + certDBEntrySubject *entry; + int ret; + + entry = ReadDBSubjectEntry(handle, derSubject); + + if ( entry == NULL ) { + return(SECFailure); + } + + ret = entry->ncerts; + + DestroyDBEntry((certDBEntry *)entry); + + return(ret); +} + +SECStatus +CERT_TraversePermCertsForNickname(CERTCertDBHandle *handle, char *nickname, + CERTCertCallback cb, void *cbarg) +{ + certDBEntryNickname *nnentry = NULL; + certDBEntrySMime *smentry = NULL; + SECStatus rv; + SECItem *derSubject = NULL; + + nnentry = ReadDBNicknameEntry(handle, nickname); + if ( nnentry ) { + derSubject = &nnentry->subjectName; + } else { + smentry = ReadDBSMimeEntry(handle, nickname); + if ( smentry ) { + derSubject = &smentry->subjectName; + } + } + + if ( derSubject ) { + rv = CERT_TraversePermCertsForSubject(handle, derSubject, + cb, cbarg); + } else { + rv = SECFailure; + } + + if ( nnentry ) { + DestroyDBEntry((certDBEntry *)nnentry); + } + if ( smentry ) { + DestroyDBEntry((certDBEntry *)smentry); + } + + return(rv); +} + +int +CERT_NumPermCertsForNickname(CERTCertDBHandle *handle, char *nickname) +{ + certDBEntryNickname *entry; + int ret; + + entry = ReadDBNicknameEntry(handle, nickname); + + if ( entry ) { + ret = CERT_NumPermCertsForSubject(handle, &entry->subjectName); + DestroyDBEntry((certDBEntry *)entry); + } else { + ret = 0; + } + return(ret); +} + +int +CERT_NumCertsForCertSubject(CERTCertificate *cert) +{ + int ret = 0; + + if ( cert->subjectList ) { + ret = cert->subjectList->ncerts; + } + return(ret); +} + +int +CERT_NumPermCertsForCertSubject(CERTCertificate *cert) +{ + int ret = 0; + + if ( cert->subjectList ) { + if ( cert->subjectList->entry ) { + ret = cert->subjectList->entry->ncerts; + } + } + return(ret); +} + +SECStatus +CERT_TraverseCertsForSubject(CERTCertDBHandle *handle, + CERTSubjectList *subjectList, + CERTCertCallback cb, void *cbarg) +{ + CERTSubjectNode *node; + CERTCertificate *cert; + SECStatus rv = SECSuccess; + + CERT_LockDB(handle); + + node = subjectList->head; + while ( node ) { + + cert = CERT_FindCertByKeyNoLocking(handle, &node->certKey); + + PORT_Assert(cert != NULL); + + if ( cert != NULL ) { + rv = (* cb)(cert, cbarg); + CERT_DestroyCertificateNoLocking(cert); + if ( rv == SECFailure ) { + break; + } + } + + node = node->next; + } + + CERT_UnlockDB(handle); + + return(rv); +} + + +/* + * Given a cert, find the cert with the same subject name that + * has the given key usage. If the given cert has the correct keyUsage, then + * return it, otherwise search the list in order. + */ +CERTCertificate * +CERT_FindCertByUsage(CERTCertificate *basecert, unsigned int requiredKeyUsage) +{ + CERTSubjectNode *node; + CERTCertificate *cert; + CERTSubjectList *subjectList; + + if ( ( basecert->keyUsage & requiredKeyUsage ) == requiredKeyUsage ) { + return(CERT_DupCertificate(basecert)); + } + + CERT_LockDB(basecert->dbhandle); + + subjectList = basecert->subjectList; + + node = subjectList->head; + while ( node ) { + + cert = CERT_FindCertByKeyNoLocking(basecert->dbhandle, &node->certKey); + + PORT_Assert(cert != NULL); + + if ( cert != NULL ) { + if ( ( cert->keyUsage & requiredKeyUsage ) == + requiredKeyUsage ) { + CERT_UnlockDB(basecert->dbhandle); + return(cert); + } + + CERT_DestroyCertificateNoLocking(cert); + } + + node = node->next; + } + + CERT_UnlockDB(basecert->dbhandle); + + return(NULL); +} + + +/* + * add a nickname to a cert that doesn't have one + */ +static SECStatus +AddNicknameToPermCert(CERTCertificate *cert, char *nickname) +{ + certDBEntryCert *entry; + int rv; + + PORT_Assert(cert->isperm); + if ( !cert->isperm ) { + goto loser; + } + + entry = cert->dbEntry; + PORT_Assert(entry != NULL); + if ( entry == NULL ) { + goto loser; + } + + entry->nickname = PORT_ArenaStrdup(entry->common.arena, nickname); + + rv = WriteDBCertEntry(cert->dbhandle, entry); + if ( rv ) { + goto loser; + } + + cert->nickname = PORT_ArenaStrdup(cert->arena, nickname); + return(SECSuccess); + +loser: + return(SECFailure); +} + +/* + * add a nickname to a cert that is already in the perm database, but doesn't + * have one yet (it is probably an e-mail cert). + */ +SECStatus +CERT_AddPermNickname(CERTCertificate *cert, char *nickname) +{ + SECStatus rv; + + CERT_LockDB(cert->dbhandle); + + PORT_Assert(cert->nickname == NULL); + PORT_Assert(cert->isperm); + PORT_Assert(cert->subjectList != NULL); + PORT_Assert(cert->subjectList->entry != NULL); + + if ( cert->nickname != NULL ) { + goto done; + } + + if ( cert->subjectList == NULL ) { + goto loser; + } + + if ( cert->subjectList->entry == NULL ) { + goto loser; + } + + if ( cert->subjectList->entry->nickname == NULL ) { + /* no nickname for subject */ + rv = AddNicknameToSubject(cert, nickname); + if ( rv != SECSuccess ) { + goto loser; + } + rv = AddNicknameToPermCert(cert, nickname); + if ( rv != SECSuccess ) { + goto loser; + } + rv = SEC_AddTempNickname(cert->dbhandle, nickname, + &cert->derSubject); + if ( rv != SECSuccess ) { + goto loser; + } + + } else { + /* subject already has a nickname */ + rv = AddNicknameToPermCert(cert, cert->subjectList->entry->nickname); + if ( rv != SECSuccess ) { + goto loser; + } + } + +done: + CERT_UnlockDB(cert->dbhandle); + return(SECSuccess); +loser: + CERT_UnlockDB(cert->dbhandle); + return(SECFailure); +} + +static certDBEntryCert * +AddCertToPermDB(CERTCertDBHandle *handle, CERTCertificate *cert, + char *nickname, CERTCertTrust *trust) +{ + certDBEntryCert *certEntry = NULL; + certDBEntryNickname *nicknameEntry = NULL; + certDBEntrySubject *subjectEntry = NULL; + int state = 0; + SECStatus rv; + PRBool donnentry = PR_FALSE; + + if ( nickname ) { + donnentry = PR_TRUE; + } + + if ( cert->subjectList != NULL ) { + if ( cert->subjectList->entry != NULL ) { + if ( cert->subjectList->entry->ncerts > 0 ) { + /* of other certs with same subject exist, then they already + * have a nickname, so don't add a new one. + */ + donnentry = PR_FALSE; + nickname = cert->subjectList->entry->nickname; + } + } + } + + certEntry = NewDBCertEntry(&cert->derCert, nickname, trust, 0); + if ( certEntry == NULL ) { + goto loser; + } + + if ( donnentry ) { + nicknameEntry = NewDBNicknameEntry(nickname, &cert->derSubject, 0); + if ( nicknameEntry == NULL ) { + goto loser; + } + } + + rv = WriteDBCertEntry(handle, certEntry); + if ( rv != SECSuccess ) { + goto loser; + } + state = 1; + + if ( nicknameEntry ) { + rv = WriteDBNicknameEntry(handle, nicknameEntry); + if ( rv != SECSuccess ) { + goto loser; + } + } + + state = 2; + + /* add to or create new subject entry */ + if ( cert->subjectList ) { + rv = AddPermSubjectNode(cert, nickname); + if ( rv != SECSuccess ) { + goto loser; + } + } else { + /* make a new subject entry - this case is only used when updating + * an old version of the database. This is OK because the oldnickname + * db format didn't allow multiple certs with the same subject. + */ + subjectEntry = NewDBSubjectEntry(&cert->derSubject, &cert->certKey, + &cert->subjectKeyID, nickname, + NULL, 0); + if ( subjectEntry == NULL ) { + goto loser; + } + rv = WriteDBSubjectEntry(handle, subjectEntry); + if ( rv != SECSuccess ) { + goto loser; + } + } + + state = 3; + + if ( nicknameEntry ) { + DestroyDBEntry((certDBEntry *)nicknameEntry); + } + + if ( subjectEntry ) { + DestroyDBEntry((certDBEntry *)subjectEntry); + } + + return(certEntry); + +loser: + /* don't leave partial entry in the database */ + if ( state > 0 ) { + rv = DeleteDBCertEntry(handle, &cert->certKey); + } + if ( ( state > 1 ) && donnentry ) { + rv = DeleteDBNicknameEntry(handle, nickname); + } + if ( state > 2 ) { + rv = DeleteDBSubjectEntry(handle, &cert->derSubject); + } + if ( certEntry ) { + DestroyDBEntry((certDBEntry *)certEntry); + } + if ( nicknameEntry ) { + DestroyDBEntry((certDBEntry *)nicknameEntry); + } + if ( subjectEntry ) { + DestroyDBEntry((certDBEntry *)subjectEntry); + } + + return(NULL); +} + +/* + * NOTE - Version 6 DB did not go out to the real world in a release, + * so we can remove this function in a later release. + */ +static SECStatus +UpdateV6DB(CERTCertDBHandle *handle, DB *updatedb) +{ + int ret; + DBT key, data; + unsigned char *buf, *tmpbuf = NULL; + certDBEntryType type; + certDBEntryNickname *nnEntry = NULL; + certDBEntrySubject *subjectEntry = NULL; + certDBEntrySMime *emailEntry = NULL; + char *nickname; + char *emailAddr; + SECStatus rv; + + /* + * Sequence through the old database and copy all of the entries + * to the new database. Subject name entries will have the new + * fields inserted into them (with zero length). + */ + ret = (* updatedb->seq)(updatedb, &key, &data, R_FIRST); + if ( ret ) { + return(SECFailure); + } + + do { + buf = (unsigned char *)data.data; + + if ( data.size >= 3 ) { + if ( buf[0] == 6 ) { /* version number */ + type = (certDBEntryType)buf[1]; + if ( type == certDBEntryTypeSubject ) { + /* expando subjecto entrieo */ + tmpbuf = (unsigned char *)PORT_Alloc(data.size + 4); + if ( tmpbuf ) { + /* copy header stuff */ + PORT_Memcpy(tmpbuf, buf, SEC_DB_ENTRY_HEADER_LEN + 2); + /* insert 4 more bytes of zero'd header */ + PORT_Memset(&tmpbuf[SEC_DB_ENTRY_HEADER_LEN + 2], + 0, 4); + /* copy rest of the data */ + PORT_Memcpy(&tmpbuf[SEC_DB_ENTRY_HEADER_LEN + 6], + &buf[SEC_DB_ENTRY_HEADER_LEN + 2], + data.size - (SEC_DB_ENTRY_HEADER_LEN + 2)); + + data.data = (void *)tmpbuf; + data.size += 4; + buf = tmpbuf; + } + } else if ( type == certDBEntryTypeCert ) { + /* expando certo entrieo */ + tmpbuf = (unsigned char *)PORT_Alloc(data.size + 3); + if ( tmpbuf ) { + /* copy header stuff */ + PORT_Memcpy(tmpbuf, buf, SEC_DB_ENTRY_HEADER_LEN); + + /* copy trust flage, setting msb's to 0 */ + tmpbuf[SEC_DB_ENTRY_HEADER_LEN] = 0; + tmpbuf[SEC_DB_ENTRY_HEADER_LEN+1] = + buf[SEC_DB_ENTRY_HEADER_LEN]; + tmpbuf[SEC_DB_ENTRY_HEADER_LEN+2] = 0; + tmpbuf[SEC_DB_ENTRY_HEADER_LEN+3] = + buf[SEC_DB_ENTRY_HEADER_LEN+1]; + tmpbuf[SEC_DB_ENTRY_HEADER_LEN+4] = 0; + tmpbuf[SEC_DB_ENTRY_HEADER_LEN+5] = + buf[SEC_DB_ENTRY_HEADER_LEN+2]; + + /* copy rest of the data */ + PORT_Memcpy(&tmpbuf[SEC_DB_ENTRY_HEADER_LEN + 6], + &buf[SEC_DB_ENTRY_HEADER_LEN + 3], + data.size - (SEC_DB_ENTRY_HEADER_LEN + 3)); + + data.data = (void *)tmpbuf; + data.size += 3; + buf = tmpbuf; + } + + } + + /* update the record version number */ + buf[0] = CERT_DB_FILE_VERSION; + + /* copy to the new database */ + ret = certdb_Put(handle->permCertDB, &key, &data, 0); + if ( tmpbuf ) { + PORT_Free(tmpbuf); + tmpbuf = NULL; + } + } + } + } while ( (* updatedb->seq)(updatedb, &key, &data, R_NEXT) == 0 ); + + ret = certdb_Sync(handle->permCertDB, 0); + + ret = (* updatedb->seq)(updatedb, &key, &data, R_FIRST); + if ( ret ) { + return(SECFailure); + } + + do { + buf = (unsigned char *)data.data; + + if ( data.size >= 3 ) { + if ( buf[0] == CERT_DB_FILE_VERSION ) { /* version number */ + type = (certDBEntryType)buf[1]; + if ( type == certDBEntryTypeNickname ) { + nickname = &((char *)key.data)[1]; + + /* get the matching nickname entry in the new DB */ + nnEntry = ReadDBNicknameEntry(handle, nickname); + if ( nnEntry == NULL ) { + goto endloop; + } + + /* find the subject entry pointed to by nickname */ + subjectEntry = ReadDBSubjectEntry(handle, + &nnEntry->subjectName); + if ( subjectEntry == NULL ) { + goto endloop; + } + + subjectEntry->nickname = + (char *)PORT_ArenaAlloc(subjectEntry->common.arena, + key.size - 1); + if ( subjectEntry->nickname ) { + PORT_Memcpy(subjectEntry->nickname, nickname, + key.size - 1); + rv = WriteDBSubjectEntry(handle, subjectEntry); + } + } else if ( type == certDBEntryTypeSMimeProfile ) { + emailAddr = &((char *)key.data)[1]; + + /* get the matching smime entry in the new DB */ + emailEntry = ReadDBSMimeEntry(handle, emailAddr); + if ( emailEntry == NULL ) { + goto endloop; + } + + /* find the subject entry pointed to by nickname */ + subjectEntry = ReadDBSubjectEntry(handle, + &emailEntry->subjectName); + if ( subjectEntry == NULL ) { + goto endloop; + } + + subjectEntry->nickname = + (char *)PORT_ArenaAlloc(subjectEntry->common.arena, + key.size - 1); + if ( subjectEntry->emailAddr ) { + PORT_Memcpy(subjectEntry->emailAddr, emailAddr, + key.size - 1); + rv = WriteDBSubjectEntry(handle, subjectEntry); + } + } + +endloop: + if ( subjectEntry ) { + DestroyDBEntry((certDBEntry *)subjectEntry); + subjectEntry = NULL; + } + if ( nnEntry ) { + DestroyDBEntry((certDBEntry *)nnEntry); + nnEntry = NULL; + } + if ( emailEntry ) { + DestroyDBEntry((certDBEntry *)emailEntry); + emailEntry = NULL; + } + } + } + } while ( (* updatedb->seq)(updatedb, &key, &data, R_NEXT) == 0 ); + + ret = certdb_Sync(handle->permCertDB, 0); + + (* updatedb->close)(updatedb); + return(SECSuccess); +} + + +static SECStatus +updateV5Callback(CERTCertificate *cert, SECItem *k, void *pdata) +{ + CERTCertDBHandle *handle; + certDBEntryCert *entry; + CERTCertTrust *trust; + + handle = (CERTCertDBHandle *)pdata; + trust = &cert->dbEntry->trust; + + /* SSL user certs can be used for email if they have an email addr */ + if ( cert->emailAddr && ( trust->sslFlags & CERTDB_USER ) && + ( trust->emailFlags == 0 ) ) { + trust->emailFlags = CERTDB_USER; + } + + entry = AddCertToPermDB(handle, cert, cert->dbEntry->nickname, + &cert->dbEntry->trust); + if ( entry ) { + DestroyDBEntry((certDBEntry *)entry); + } + + return(SECSuccess); +} + +static SECStatus +UpdateV5DB(CERTCertDBHandle *handle, DB *updatedb) +{ + CERTCertDBHandle updatehandle; + SECStatus rv; + + updatehandle.permCertDB = updatedb; + updatehandle.dbMon = PR_NewMonitor(); + + rv = SEC_TraversePermCerts(&updatehandle, updateV5Callback, + (void *)handle); + + PR_DestroyMonitor(updatehandle.dbMon); + + return(rv); +} + +static SECStatus +UpdateV4DB(CERTCertDBHandle *handle, DB *updatedb) +{ + DBT key, data; + certDBEntryCert *entry, *entry2; + SECItem derSubject; + int ret; + PRArenaPool *arena = NULL; + CERTCertificate *cert; + + ret = (* updatedb->seq)(updatedb, &key, &data, R_FIRST); + + if ( ret ) { + return(SECFailure); + } + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + return(SECFailure); + } + + do { + if ( data.size != 1 ) { /* skip version number */ + + /* decode the old DB entry */ + entry = (certDBEntryCert *)DecodeV4DBCertEntry((unsigned char*)data.data, data.size); + derSubject.data = NULL; + + if ( entry ) { + cert = CERT_DecodeDERCertificate(&entry->derCert, PR_TRUE, + entry->nickname); + + if ( cert != NULL ) { + /* add to new database */ + entry2 = AddCertToPermDB(handle, cert, entry->nickname, + &entry->trust); + + CERT_DestroyCertificate(cert); + if ( entry2 ) { + DestroyDBEntry((certDBEntry *)entry2); + } + } + DestroyDBEntry((certDBEntry *)entry); + } + } + } while ( (* updatedb->seq)(updatedb, &key, &data, R_NEXT) == 0 ); + + PORT_FreeArena(arena, PR_FALSE); + (* updatedb->close)(updatedb); + return(SECSuccess); +} + +/* + * return true if a database key conflict exists + */ +PRBool +SEC_CertDBKeyConflict(SECItem *derCert, CERTCertDBHandle *handle) +{ + SECStatus rv; + DBT tmpdata; + DBT namekey; + int ret; + SECItem keyitem; + PRArenaPool *arena = NULL; + SECItem derKey; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + /* get the db key of the cert */ + rv = CERT_KeyFromDERCert(arena, derCert, &derKey); + if ( rv != SECSuccess ) { + goto loser; + } + + rv = EncodeDBCertKey(&derKey, arena, &keyitem); + if ( rv != SECSuccess ) { + goto loser; + } + + namekey.data = keyitem.data; + namekey.size = keyitem.len; + + /* lookup in the temporary database */ + ret = certdb_Get(handle->tempCertDB, &namekey, &tmpdata, 0); + + if ( ret == 0 ) { /* found in temp database */ + goto loser; + } else { /* not found in temporary database */ + ret = certdb_Get(handle->permCertDB, &namekey, &tmpdata, 0); + if ( ret == 0 ) { + goto loser; + } + } + + PORT_FreeArena(arena, PR_FALSE); + + return(PR_FALSE); +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(PR_TRUE); +} + +#ifdef NOTDEF +/* + * return true if a subject name conflict exists + * NOTE: caller must have already made sure that this exact cert + * doesn't exist in the DB + */ +PRBool +SEC_CertSubjectConflict(SECItem *derCert, CERTCertDBHandle *handle) +{ + SECStatus rv; + DBT tmpdata; + DBT namekey; + int ret; + SECItem keyitem; + PRArenaPool *arena = NULL; + SECItem derName; + + derName.data = NULL; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + /* get the subject name of the cert */ + rv = CERT_NameFromDERCert(derCert, &derName); + if ( rv != SECSuccess ) { + goto loser; + } + + rv = EncodeDBSubjectKey(&derName, arena, &keyitem); + if ( rv != SECSuccess ) { + goto loser; + } + + namekey.data = keyitem.data; + namekey.size = keyitem.len; + + /* lookup in the temporary database */ + ret = certdb_Get(handle->tempCertDB, &namekey, &tmpdata, 0); + + if ( ret == 0 ) { /* found in temp database */ + return(PR_TRUE); + } else { /* not found in temporary database */ + ret = certdb_Get(handle->permCertDB, &namekey, &tmpdata, 0); + if ( ret == 0 ) { + return(PR_TRUE); + } + } + + PORT_FreeArena(arena, PR_FALSE); + PORT_Free(derName.data); + + return(PR_FALSE); +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + if ( derName.data ) { + PORT_Free(derName.data); + } + + return(PR_TRUE); +} +#endif + +/* + * return true if a nickname conflict exists + * NOTE: caller must have already made sure that this exact cert + * doesn't exist in the DB + */ +PRBool +SEC_CertNicknameConflict(char *nickname, SECItem *derSubject, + CERTCertDBHandle *handle) +{ + PRBool rv; + certDBEntryNickname *entry; + + if ( nickname == NULL ) { + return(PR_FALSE); + } + + entry = ReadDBNicknameEntry(handle, nickname); + + if ( entry == NULL ) { + /* no entry for this nickname, so no conflict */ + return(PR_FALSE); + } + + rv = PR_TRUE; + if ( SECITEM_CompareItem(derSubject, &entry->subjectName) == SECEqual ) { + /* if subject names are the same, then no conflict */ + rv = PR_FALSE; + } + + DestroyDBEntry((certDBEntry *)entry); + return(rv); +} + +/* + * Open the certificate database and index databases. Create them if + * they are not there or bad. + */ +SECStatus +SEC_OpenPermCertDB(CERTCertDBHandle *handle, PRBool readOnly, + CERTDBNameFunc namecb, void *cbarg) +{ + SECStatus rv; + int openflags; + certDBEntryVersion *versionEntry = NULL; + DB *updatedb = NULL; + char *tmpname; + char *certdbname; + PRBool updated = PR_FALSE; + PRBool forceUpdate = PR_FALSE; + + certdbname = (* namecb)(cbarg, CERT_DB_FILE_VERSION); + if ( certdbname == NULL ) { + return(SECFailure); + } + + if ( readOnly ) { + openflags = O_RDONLY; + } else { + openflags = O_RDWR; + } + + /* + * first open the permanent file based database. + */ + handle->permCertDB = dbopen( certdbname, openflags, 0600, DB_HASH, 0 ); + + /* check for correct version number */ + if ( handle->permCertDB ) { + versionEntry = ReadDBVersionEntry(handle); + + if ( versionEntry == NULL ) { + /* no version number */ + certdb_Close(handle->permCertDB); + handle->permCertDB = 0; + } else if ( versionEntry->common.version != CERT_DB_FILE_VERSION ) { + /* wrong version number, can't update in place */ + DestroyDBEntry((certDBEntry *)versionEntry); + return(SECFailure); + } + + } + + + /* if first open fails, try to create a new DB */ + if ( handle->permCertDB == NULL ) { + + /* don't create if readonly */ + if ( readOnly ) { + goto loser; + } + + handle->permCertDB = dbopen(certdbname, + O_RDWR | O_CREAT | O_TRUNC, + 0600, DB_HASH, 0); + + /* if create fails then we lose */ + if ( handle->permCertDB == 0 ) { + goto loser; + } + + versionEntry = NewDBVersionEntry(0); + if ( versionEntry == NULL ) { + goto loser; + } + + rv = WriteDBVersionEntry(handle, versionEntry); + + DestroyDBEntry((certDBEntry *)versionEntry); + + if ( rv != SECSuccess ) { + goto loser; + } + + /* try to upgrade old db here */ + tmpname = (* namecb)(cbarg, 6); /* get v6 db name */ + if ( tmpname ) { + updatedb = dbopen( tmpname, O_RDONLY, 0600, DB_HASH, 0 ); + PORT_Free(tmpname); + if ( updatedb ) { + rv = UpdateV6DB(handle, updatedb); + if ( rv != SECSuccess ) { + goto loser; + } + updated = PR_TRUE; + } else { /* no v6 db, so try v5 db */ + tmpname = (* namecb)(cbarg, 5); /* get v5 db name */ + if ( tmpname ) { + updatedb = dbopen( tmpname, O_RDONLY, 0600, DB_HASH, 0 ); + PORT_Free(tmpname); + if ( updatedb ) { + rv = UpdateV5DB(handle, updatedb); + if ( rv != SECSuccess ) { + goto loser; + } + updated = PR_TRUE; + } else { /* no v5 db, so try v4 db */ + /* try to upgrade v4 db */ + tmpname = (* namecb)(cbarg, 4); /* get v4 db name */ + if ( tmpname ) { + updatedb = dbopen( tmpname, O_RDONLY, 0600, + DB_HASH, 0 ); + PORT_Free(tmpname); + if ( updatedb ) { + rv = UpdateV4DB(handle, updatedb); + if ( rv != SECSuccess ) { + goto loser; + } + forceUpdate = PR_TRUE; + updated = PR_TRUE; + } + } + } + } + } + } + + /* initialize the database with our well known certificates + * or in the case of update, just fall down to CERT_AddNewCerts() + * below. + * Note - if we are updating a really old database, then we try + * to push all of the certs into it. + */ + if ( ( !updated ) || forceUpdate ) { + rv = CERT_InitCertDB(handle); + if ( rv != SECSuccess ) { + goto loser; + } + } + } + + rv = CERT_AddNewCerts(handle); + + return (SECSuccess); + +loser: + + PORT_SetError(SEC_ERROR_BAD_DATABASE); + + if ( handle->permCertDB ) { + certdb_Close(handle->permCertDB); + handle->permCertDB = 0; + } + + return(SECFailure); +} + +/* + * delete all DB records associated with a particular certificate + */ +static SECStatus +DeletePermCert(CERTCertificate *cert) +{ + SECStatus rv; + SECStatus ret; + + ret = SECSuccess; + + rv = DeleteDBCertEntry(cert->dbhandle, &cert->certKey); + if ( rv != SECSuccess ) { + ret = SECFailure; + } + + if ( cert->nickname ) { + rv = DeleteDBNicknameEntry(cert->dbhandle, cert->nickname); + if ( rv != SECSuccess ) { + ret = SECFailure; + } + } + + rv = RemovePermSubjectNode(cert); + + return(ret); +} + +/* + * Delete a certificate from the permanent database. + */ +SECStatus +SEC_DeletePermCertificate(CERTCertificate *cert) +{ + SECStatus rv; + + if ( !cert->isperm ) { + return(SECSuccess); + } + CERT_LockDB(cert->dbhandle); + /* delete the records from the permanent database */ + rv = DeletePermCert(cert); + + /* no longer permanent */ + cert->isperm = PR_FALSE; + + /* get rid of dbcert and stuff pointing to it */ + DestroyDBEntry((certDBEntry *)cert->dbEntry); + cert->dbEntry = NULL; + cert->trust = NULL; + + /* delete it from the temporary database too. It will remain in + * memory until all references go away. + */ + if (cert->slot) { + /* If it's owned by a PKCS #11 slot, don't deleted if from the temp DB just + * yet... rv inherited from DeletePermCert (as if anyone checks the return + * code from this function anyway. */ + CERT_DestroyCertificateNoLocking(cert); + rv = SECSuccess; + } else { + rv = CERT_DeleteTempCertificate(cert); + } + + CERT_UnlockDB(cert->dbhandle); + return(rv); +} + +/* + * Lookup a certificate in the databases. + */ +certDBEntryCert * +SEC_FindPermCertByKey(CERTCertDBHandle *handle, SECItem *key) +{ + return(ReadDBCertEntry(handle, key)); +} + +/* + * Lookup a certificate in the database by name + */ +certDBEntryCert * +SEC_FindPermCertByName(CERTCertDBHandle *handle, SECItem *derSubject) +{ + certDBEntrySubject *subjectEntry; + certDBEntryCert *certEntry; + + subjectEntry = ReadDBSubjectEntry(handle, derSubject); + + if ( subjectEntry == NULL ) { + goto loser; + } + + certEntry = ReadDBCertEntry(handle, &subjectEntry->certKeys[0]); + DestroyDBEntry((certDBEntry *)subjectEntry); + + return(certEntry); + +loser: + return(NULL); +} + +/* + * Lookup a certificate in the database by nickname + */ +certDBEntryCert * +SEC_FindPermCertByNickname(CERTCertDBHandle *handle, char *nickname) +{ + certDBEntryNickname *nicknameEntry; + certDBEntryCert *certEntry; + + nicknameEntry = ReadDBNicknameEntry(handle, nickname); + + if ( nicknameEntry == NULL ) { + goto loser; + } + + certEntry = SEC_FindPermCertByName(handle, &nicknameEntry->subjectName); + DestroyDBEntry((certDBEntry *)nicknameEntry); + + return(certEntry); + +loser: + return(NULL); +} + +/* + * Traverse all of the entries in the database of a particular type + * call the given function for each one. + */ +SECStatus +SEC_TraverseDBEntries(CERTCertDBHandle *handle, + certDBEntryType type, + SECStatus (* callback)(SECItem *data, SECItem *key, + certDBEntryType type, void *pdata), + void *udata ) +{ + DBT data; + DBT key; + SECStatus rv; + int ret; + SECItem dataitem; + SECItem keyitem; + unsigned char *buf; + unsigned char *keybuf; + + ret = certdb_Seq(handle->permCertDB, &key, &data, R_FIRST); + + if ( ret ) { + return(SECFailure); + } + + do { + buf = (unsigned char *)data.data; + + if ( buf[1] == (unsigned char)type ) { + dataitem.len = data.size; + dataitem.data = buf; + dataitem.type = siBuffer; + keyitem.len = key.size - SEC_DB_KEY_HEADER_LEN; + keybuf = (unsigned char *)key.data; + keyitem.data = &keybuf[SEC_DB_KEY_HEADER_LEN]; + keyitem.type = siBuffer; + + rv = (* callback)(&dataitem, &keyitem, type, udata); + if ( rv != SECSuccess ) { + return(rv); + } + } + } while ( certdb_Seq(handle->permCertDB, &key, &data, R_NEXT) == 0 ); + + return(SECSuccess); +} + +typedef struct { + PermCertCallback certfunc; + CERTCertDBHandle *handle; + void *data; +} PermCertCallbackState; + +/* + * traversal callback to decode certs and call callers callback + */ +static SECStatus +certcallback(SECItem *dbdata, SECItem *dbkey, certDBEntryType type, void *data) +{ + PermCertCallbackState *mystate; + SECStatus rv; + certDBEntryCert entry; + SECItem entryitem; + CERTCertificate *cert; + PRArenaPool *arena = NULL; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + mystate = (PermCertCallbackState *)data; + entry.common.version = (unsigned int)dbdata->data[0]; + entry.common.type = (certDBEntryType)dbdata->data[1]; + entry.common.flags = (unsigned int)dbdata->data[2]; + entry.common.arena = arena; + + entryitem.len = dbdata->len - SEC_DB_ENTRY_HEADER_LEN; + entryitem.data = &dbdata->data[SEC_DB_ENTRY_HEADER_LEN]; + + rv = DecodeDBCertEntry(&entry, &entryitem); + if (rv != SECSuccess ) { + goto loser; + } + entry.derCert.type = siBuffer; + + cert = CERT_DecodeDERCertificate(&entry.derCert, PR_FALSE, + entry.nickname); + cert->dbEntry = &entry; + cert->trust = &entry.trust; + cert->dbhandle = mystate->handle; + + if ( CERT_IsCACert(cert, NULL) || + (( cert->trust->sslFlags & CERTDB_VALID_CA ) || + ( cert->trust->emailFlags & CERTDB_VALID_CA ) || + ( cert->trust->objectSigningFlags & CERTDB_VALID_CA)) ) { + cert->nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER; + } + + rv = (* mystate->certfunc)(cert, dbkey, mystate->data); + + /* arena destroyed by SEC_DestroyCert */ + CERT_DestroyCertificateNoLocking(cert); + + return(rv); + +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + return(SECFailure); +} + +/* + * Traverse all of the certificates in the permanent database and + * call the given function for each one; expect the caller to have lock. + */ +static SECStatus +TraversePermCertsNoLocking(CERTCertDBHandle *handle, + SECStatus (* certfunc)(CERTCertificate *cert, + SECItem *k, + void *pdata), + void *udata ) +{ + SECStatus rv; + PermCertCallbackState mystate; + + mystate.certfunc = certfunc; + mystate.handle = handle; + mystate.data = udata; + rv = SEC_TraverseDBEntries(handle, certDBEntryTypeCert, certcallback, + (void *)&mystate); + + return(rv); +} + +/* + * Traverse all of the certificates in the permanent database and + * call the given function for each one. + */ +SECStatus +SEC_TraversePermCerts(CERTCertDBHandle *handle, + SECStatus (* certfunc)(CERTCertificate *cert, SECItem *k, + void *pdata), + void *udata ) +{ + SECStatus rv; + + CERT_LockDB(handle); + rv = TraversePermCertsNoLocking(handle, certfunc, udata); + CERT_UnlockDB(handle); + + return(rv); +} + + + +/* + * Close the database + */ +void +CERT_ClosePermCertDB(CERTCertDBHandle *handle) +{ + if ( handle ) { + if ( handle->permCertDB ) { + if ( handle->statusConfig ) { + PORT_Assert(handle->statusConfig->statusDestroy != NULL); + (void) (* handle->statusConfig->statusDestroy)(handle->statusConfig); + handle->statusConfig = NULL; /* Destroy frees the structure */ + PORT_Assert(handle->statusConfig == NULL); + } + certdb_Close( handle->permCertDB ); + handle->permCertDB = 0; + } + } + return; +} + +/* + * Get the trust attributes from a certificate + */ +SECStatus +CERT_GetCertTrust(CERTCertificate *cert, CERTCertTrust *trust) +{ + SECStatus rv; + + CERT_LockCertTrust(cert); + + if ( cert->trust == NULL ) { + rv = SECFailure; + } else { + *trust = *cert->trust; + rv = SECSuccess; + } + + CERT_UnlockCertTrust(cert); + return(rv); +} + +/* + * Change the trust attributes of a certificate and make them permanent + * in the database. + */ +SECStatus +CERT_ChangeCertTrust(CERTCertDBHandle *handle, CERTCertificate *cert, + CERTCertTrust *trust) +{ + certDBEntryCert *entry; + int rv; + SECStatus ret; + + CERT_LockDB(handle); + CERT_LockCertTrust(cert); + /* only set the trust on permanent certs */ + if ( cert->trust == NULL ) { + ret = SECFailure; + goto done; + } + + *cert->trust = *trust; + if ( cert->dbEntry == NULL ) { + ret = SECSuccess; /* not in permanent database */ + goto done; + } + + entry = cert->dbEntry; + entry->trust = *trust; + + rv = WriteDBCertEntry(handle, entry); + if ( rv ) { + ret = SECFailure; + goto done; + } + + ret = SECSuccess; + +done: + CERT_UnlockCertTrust(cert); + CERT_UnlockDB(handle); + return(ret); +} + + +SECStatus +CERT_AddTempCertToPerm(CERTCertificate *cert, char *nickname, + CERTCertTrust *trust) +{ + char *oldnn; + certDBEntryCert *entry; + SECStatus rv; + PRBool conflict; + SECStatus ret; + + PORT_Assert(cert->dbhandle); + + CERT_LockDB(cert->dbhandle); + + PORT_Assert(cert->istemp); + PORT_Assert(!cert->isperm); + PORT_Assert(!cert->dbEntry); + + /* don't add a conflicting nickname */ + conflict = SEC_CertNicknameConflict(nickname, &cert->derSubject, + cert->dbhandle); + if ( conflict ) { + ret = SECFailure; + goto done; + } + + /* save old nickname so that we can delete it */ + oldnn = cert->nickname; + + entry = AddCertToPermDB(cert->dbhandle, cert, nickname, trust); + + if ( entry == NULL ) { + ret = SECFailure; + goto done; + } + + cert->nickname = (entry->nickname) ? PORT_ArenaStrdup(cert->arena,entry->nickname) : NULL; + cert->trust = &entry->trust; + cert->isperm = PR_TRUE; + cert->dbEntry = entry; + + if ( nickname && oldnn && ( PORT_Strcmp(nickname, oldnn) != 0 ) ) { + /* only delete the old one if they are not the same */ + /* delete old nickname from temp database */ + rv = SEC_DeleteTempNickname(cert->dbhandle, oldnn); + if ( rv != SECSuccess ) { + /* do we care?? */ + } + } + /* add new nickname to temp database */ + if ( cert->nickname ) { + rv = SEC_AddTempNickname(cert->dbhandle, cert->nickname, + &cert->derSubject); + if ( rv != SECSuccess ) { + ret = SECFailure; + goto done; + } + } + + ret = SECSuccess; +done: + CERT_UnlockDB(cert->dbhandle); + return(ret); +} + +/* + * Open the certificate database and index databases. Create them if + * they are not there or bad. + */ +SECStatus +CERT_OpenCertDB(CERTCertDBHandle *handle, PRBool readOnly, + CERTDBNameFunc namecb, void *cbarg) +{ + int rv; + + certdb_InitDBLock(); + + handle->dbMon = PR_NewMonitor(); + PORT_Assert(handle->dbMon != NULL); + + handle->spkDigestInfo = NULL; + handle->statusConfig = NULL; + + /* + * Open the memory resident decoded cert database. + */ + handle->tempCertDB = dbopen( 0, O_RDWR | O_CREAT, 0600, DB_HASH, 0 ); + if ( !handle->tempCertDB ) { + goto loser; + } + + rv = SEC_OpenPermCertDB(handle, readOnly, namecb, cbarg); + if ( rv ) { + goto loser; + } + + return (SECSuccess); + +loser: + + PORT_SetError(SEC_ERROR_BAD_DATABASE); + + if ( handle->tempCertDB ) { + certdb_Close(handle->tempCertDB); + handle->tempCertDB = 0; + } + + return(SECFailure); +} + +static char * +certDBFilenameCallback(void *arg, int dbVersion) +{ + return((char *)arg); +} + +SECStatus +CERT_OpenCertDBFilename(CERTCertDBHandle *handle, char *certdbname, + PRBool readOnly) +{ + return(CERT_OpenCertDB(handle, readOnly, certDBFilenameCallback, + (void *)certdbname)); +} + +/* + * Add a nickname to the temp database + */ +SECStatus +SEC_AddTempNickname(CERTCertDBHandle *handle, char *nickname, + SECItem *subjectName) +{ + DBT namekey; + int ret; + SECItem nameitem; + SECStatus rv; + DBT keydata; + PRArenaPool *arena = NULL; + SECItem tmpitem; + + PORT_Assert(nickname != NULL); + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + rv = EncodeDBNicknameKey(nickname, arena, &nameitem); + if ( rv != SECSuccess ) { + goto loser; + } + + namekey.data = nameitem.data; + namekey.size = nameitem.len; + + /* see if an entry already exists */ + ret = certdb_Get(handle->tempCertDB, &namekey, &keydata, 0); + + if ( ret == 0 ) { + /* found in temp database */ + tmpitem.data = (unsigned char*)keydata.data; + tmpitem.len = keydata.size; + + if ( SECITEM_CompareItem(subjectName, &tmpitem) == SECEqual ) { + /* same subject name */ + goto done; + } else { + /* different subject name is an error */ + goto loser; + } + } + + keydata.data = subjectName->data; + keydata.size = subjectName->len; + + /* put into temp byname index */ + ret = certdb_Put(handle->tempCertDB, &namekey, &keydata, R_NOOVERWRITE); + + if ( ret ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + +done: + PORT_FreeArena(arena, PR_FALSE); + return(SECSuccess); + +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + return(SECFailure); +} + +SECStatus +SEC_DeleteTempNickname(CERTCertDBHandle *handle, char *nickname) +{ + DBT namekey; + SECStatus rv; + PRArenaPool *arena = NULL; + SECItem nameitem; + int ret; + + PORT_Assert(nickname != NULL); + if ( nickname == NULL ) { + return(SECSuccess); + } + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* format a database key based on the nickname */ + if ( nickname ) { + rv = EncodeDBNicknameKey(nickname, arena, &nameitem); + if ( rv != SECSuccess ) { + goto loser; + } + + namekey.data = nameitem.data; + namekey.size = nameitem.len; + + ret = certdb_Del(handle->tempCertDB, &namekey, 0); + if ( ret ) { + goto loser; + } + } + + PORT_FreeArena(arena, PR_FALSE); + + return(SECSuccess); + +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + return(SECFailure); +} + +/* + * Decode a certificate and enter it into the temporary certificate database. + * Deal with nicknames correctly + * + * nickname is only used if isperm == PR_TRUE + * + * This is the private entry point, and locking is optional + */ +static CERTCertificate * +NewTempCertificate(CERTCertDBHandle *handle, SECItem *derCert, char *nickname, + PRBool isperm, PRBool copyDER, PRBool lockdb) + +{ + DBT key; + DBT data; + int status; + CERTCertificate *cert = NULL; + PRBool promoteError = PR_TRUE; + PRArenaPool *arena = NULL; + SECItem keyitem; + SECStatus rv; + + if ( isperm == PR_FALSE ) { + cert = CERT_FindCertByDERCert(handle, derCert); + if ( cert ) { + return(cert); + } + + nickname = NULL; + } + + if ( lockdb ) { + CERT_LockDB(handle); + } + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + cert = CERT_DecodeDERCertificate(derCert, copyDER, nickname ); + + if ( cert == NULL ) { + /* We want to save the decoding error here */ + promoteError = PR_FALSE; + goto loser; + } + + cert->dbhandle = handle; + + /* only save pointer to cert in database */ + data.data = &cert; + data.size = sizeof(cert); + + /* if this is a perm cert, then it is already in the subject db */ + if ( isperm == PR_FALSE ) { + /* enter into the subject index */ + rv = AddTempCertToSubjectList(cert); + if ( rv != SECSuccess ) { + goto loser; + } + /* + * Since it's not a perm cert, add it to the key hash lookup; if it + * is permanent it will either already be there or will get put there + * later along with the rest of the perm certs. A failure of the + * addition does not seem to warrant failing this whole function, + * so we intentionally ignore the returned status. + */ + (void) AddCertToSPKDigestTable(handle, cert); + } else { + cert->subjectList = FindSubjectList(cert->dbhandle, &cert->derSubject, + PR_FALSE); + } + + rv = EncodeDBCertKey(&cert->certKey, arena, &keyitem); + if ( rv != SECSuccess ) { + goto loser; + } + + key.data = keyitem.data; + key.size = keyitem.len; + + /* enter into main db */ + status = certdb_Put(handle->tempCertDB, &key, &data, R_NOOVERWRITE); + if ( status ) { + goto loser; + } + + if ( cert->nickname ) { + status = SEC_AddTempNickname(handle, cert->nickname, + &cert->derSubject); + if ( status ) { + promoteError = PR_FALSE; + goto loser; + } + } + + cert->isperm = isperm; + cert->istemp = PR_TRUE; + + PORT_FreeArena(arena, PR_FALSE); + + if ( lockdb ) { + CERT_UnlockDB(handle); + } + + return(cert); + +loser: + if ( cert ) { + CERT_DestroyCertificateNoLocking(cert); + } + + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + if ( promoteError ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + } + + if ( lockdb ) { + CERT_UnlockDB(handle); + } + + return(0); +} + +/* + * Decode a certificate and enter it into the temporary certificate database. + * Deal with nicknames correctly + * + * nickname is only used if isperm == PR_TRUE + * + * This is the public entry point and does locking. + */ +CERTCertificate * +CERT_NewTempCertificate(CERTCertDBHandle *handle, SECItem *derCert, + char *nickname, PRBool isperm, PRBool copyDER) +{ + return( NewTempCertificate(handle, derCert, nickname, isperm, copyDER, + PR_TRUE) ); +} + +/* + * Decode a permanent certificate and enter it into the temporary certificate + * database. + */ +static CERTCertificate * +SEC_AddPermCertToTemp(CERTCertDBHandle *handle, certDBEntryCert *entry) +{ + CERTCertificate *cert; + + /* we already hold the lock */ + cert = NewTempCertificate(handle, &entry->derCert, entry->nickname, + PR_TRUE, PR_TRUE, PR_FALSE); + if ( !cert ) { + return(0); + } + + cert->dbEntry = entry; + + cert->trust = &entry->trust; + + return(cert); +} + +SECStatus +CERT_DeleteTempCertificate(CERTCertificate *cert) +{ + SECStatus rv; + DBT nameKey; + CERTCertDBHandle *handle; + SECItem keyitem; + PRArenaPool *arena; + int ret; + + handle = cert->dbhandle; + + if ( !cert->istemp ) { + return(SECSuccess); + } + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + if (cert->slot) { + PK11_FreeSlot(cert->slot); + cert->slot = NULL; + cert->pkcs11ID = CK_INVALID_KEY; + } + + /* delete from subject list (also takes care of nickname) */ + rv = RemoveTempCertFromSubjectList(cert); + if ( rv != SECSuccess ) { + goto loser; + } + + if ( !cert->isperm ) { + /* + * Remove the cert from the subject public key digest table, + * though we do not care if the removal fails (perhaps meaning + * the cert wasn't even there). + */ + (void) RemoveCertFromSPKDigestTable(handle, cert); + } + + rv = EncodeDBCertKey(&cert->certKey, arena, &keyitem); + if ( rv != SECSuccess ) { + goto loser; + } + + nameKey.data = keyitem.data; + nameKey.size = keyitem.len; + /* delete the cert */ + ret = certdb_Del(handle->tempCertDB, &nameKey, 0); + if ( ret ) { + goto loser; + } + + cert->istemp = PR_FALSE; + + PORT_FreeArena(arena, PR_FALSE); + return(SECSuccess); + +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(SECFailure); +} + +/* + * Lookup a certificate in the databases. + */ +static CERTCertificate * +FindCertByKey(CERTCertDBHandle *handle, SECItem *certKey, PRBool lockdb) +{ + DBT tmpdata; + int ret; + SECItem keyitem; + DBT key; + SECStatus rv; + CERTCertificate *cert = NULL; + PRArenaPool *arena = NULL; + certDBEntryCert *entry; + PRBool locked = PR_FALSE; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + rv = EncodeDBCertKey(certKey, arena, &keyitem); + if ( rv != SECSuccess ) { + goto loser; + } + + key.data = keyitem.data; + key.size = keyitem.len; + + if ( lockdb ) { + locked = PR_TRUE; + CERT_LockDB(handle); + } + + /* lookup in the temporary database */ + ret = certdb_Get( handle->tempCertDB, &key, &tmpdata, 0 ); + + /* error accessing the database */ + if ( ret < 0 ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + if ( ret == 0 ) { /* found in temp database */ + if ( tmpdata.size != sizeof(CERTCertificate *) ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + PORT_Memcpy(&cert, tmpdata.data, tmpdata.size); + CERT_LockCertRefCount(cert); + cert->referenceCount++; + CERT_UnlockCertRefCount(cert); + } + if ( ret != 0 ) { + /* not found in temporary database */ + + /* find in perm database */ + entry = SEC_FindPermCertByKey(handle, certKey); + + if ( entry == NULL ) { + goto loser; + } + + cert = SEC_AddPermCertToTemp(handle, entry); + } + +loser: + if ( locked ) { + CERT_UnlockDB(handle); + } + + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(cert); +} + +/* + * Lookup a certificate in the databases, with locking + */ +CERTCertificate * +CERT_FindCertByKey(CERTCertDBHandle *handle, SECItem *certKey) +{ + return(FindCertByKey(handle, certKey, PR_TRUE)); +} + +/* + * Lookup a certificate in the databases without locking + */ +CERTCertificate * +CERT_FindCertByKeyNoLocking(CERTCertDBHandle *handle, SECItem *certKey) +{ + return(FindCertByKey(handle, certKey, PR_FALSE)); +} + +/* + * Generate a key from an issuerAndSerialNumber, and find the + * associated cert in the database. + */ +CERTCertificate * +CERT_FindCertByIssuerAndSN(CERTCertDBHandle *handle, CERTIssuerAndSN *issuerAndSN) +{ + SECItem certKey; + CERTCertificate *cert; + + certKey.len = issuerAndSN->serialNumber.len + issuerAndSN->derIssuer.len; + certKey.data = (unsigned char*)PORT_Alloc(certKey.len); + + if ( certKey.data == NULL ) { + return(0); + } + + /* copy the serialNumber */ + PORT_Memcpy(certKey.data, issuerAndSN->serialNumber.data, + issuerAndSN->serialNumber.len); + + /* copy the issuer */ + PORT_Memcpy( &certKey.data[issuerAndSN->serialNumber.len], + issuerAndSN->derIssuer.data, issuerAndSN->derIssuer.len); + + cert = CERT_FindCertByKey(handle, &certKey); + + PORT_Free(certKey.data); + + return(cert); +} + +/* + * Lookup a certificate in the database by name + */ +CERTCertificate * +CERT_FindCertByName(CERTCertDBHandle *handle, SECItem *name) +{ + CERTCertificate *cert = NULL; + CERTSubjectList *subjectList; + + CERT_LockDB(handle); + + subjectList = FindSubjectList(handle, name, PR_FALSE); + + if ( subjectList ) { + PORT_Assert(subjectList->head); + cert = CERT_FindCertByKeyNoLocking(handle, + &subjectList->head->certKey); + } + + CERT_UnlockDB(handle); + + return(cert); +} + +/* + * Lookup a certificate in the database by name and key ID + */ +CERTCertificate * +CERT_FindCertByKeyID(CERTCertDBHandle *handle, SECItem *name, SECItem *keyID) +{ + CERTCertificate *cert = NULL; + CERTSubjectList *subjectList; + CERTSubjectNode *node; + + CERT_LockDB(handle); + + /* find the list of certs for the given subject */ + subjectList = FindSubjectList(handle, name, PR_FALSE); + + if ( subjectList ) { + PORT_Assert(subjectList->head); + node = subjectList->head; + + /* walk through the certs until we find one with a matching key ID */ + while ( node ) { + if ( SECITEM_CompareItem(keyID, &node->keyID) == SECEqual ) { + cert = CERT_FindCertByKeyNoLocking(handle, &node->certKey); + break; + } + node = node->next; + } + } + + CERT_UnlockDB(handle); + + return(cert); +} + +/* + * look up a cert by its nickname string + */ +CERTCertificate * +CERT_FindCertByNickname(CERTCertDBHandle *handle, char *nickname) +{ + DBT tmpdata; + DBT namekey; + CERTCertificate *cert; + SECStatus rv; + int ret; + SECItem keyitem; + PRArenaPool *arena = NULL; + certDBEntryCert *entry; + + PORT_Assert(nickname != NULL); + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + rv = EncodeDBNicknameKey(nickname, arena, &keyitem); + if ( rv != SECSuccess ) { + goto loser; + } + + namekey.data = keyitem.data; + namekey.size = keyitem.len; + + /* lookup in the temporary database */ + ret = certdb_Get(handle->tempCertDB, &namekey, &tmpdata, 0); + + /* error accessing the database */ + if ( ret < 0 ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + if ( ret == 0 ) { /* found in temp database */ + SECItem nameitem; + + nameitem.len = tmpdata.size; + nameitem.data = (unsigned char *)PORT_Alloc(tmpdata.size); + if ( nameitem.data == NULL ) { + goto loser; + } + PORT_Memcpy(nameitem.data, tmpdata.data, nameitem.len); + cert = CERT_FindCertByName(handle, &nameitem); + PORT_Free(nameitem.data); + } else { /* not found in temporary database */ + + CERT_LockDB(handle); + + entry = SEC_FindPermCertByNickname(handle, nickname); + + if ( entry == NULL ) { + CERT_UnlockDB(handle); + goto loser; + } + + cert = SEC_AddPermCertToTemp(handle, entry); + CERT_UnlockDB(handle); + } + + PORT_FreeArena(arena, PR_FALSE); + + return(cert); + +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(0); +} + +/* + * look for the given DER certificate in the database + */ +CERTCertificate * +CERT_FindCertByDERCert(CERTCertDBHandle *handle, SECItem *derCert) +{ + PRArenaPool *arena; + SECItem certKey; + SECStatus rv; + CERTCertificate *cert = NULL; + + /* create a scratch arena */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + return(NULL); + } + + /* extract the database key from the cert */ + rv = CERT_KeyFromDERCert(arena, derCert, &certKey); + if ( rv != SECSuccess ) { + goto loser; + } + + /* find the certificate */ + cert = CERT_FindCertByKey(handle, &certKey); + +loser: + PORT_FreeArena(arena, PR_FALSE); + return(cert); +} + +/* + * The following is bunch of types and code to allow looking up a certificate + * by a hash of its subject public key. Because the words "hash" and "key" + * are overloaded and thus terribly confusing, I tried to disambiguate things. + * - Where I could, I used "digest" instead of "hash" when referring to + * hashing of the subject public key. The PLHashTable interfaces and + * our own HASH_Foo interfaces had to be left as is, obviously. The latter + * should be thought of as "digest" in this case. + * - There are three keys in use here -- the subject public key, the key + * used to do a lookup in the PLHashTable, and the key used to do a lookup + * in the cert database. As the latter is a fairly pervasive interface, + * I left it alone. The other two uses I changed to "spk" or "SPK" when + * referring to the subject public key, and "index" when referring to the + * key into the PLHashTable. + */ + +typedef struct SPKDigestInfoStr { + PLHashTable *table; + PRBool permPopulated; +} SPKDigestInfo; + +/* + * Since the key hash information is "hidden" (in a void pointer in the handle) + * these macros with the appropriate casts make it easy to get at the parts. + */ +#define SPK_DIGEST_TABLE(handle) \ + (((SPKDigestInfo *)(handle->spkDigestInfo))->table) + +/* + * Create the key hash lookup table. Note that the table, and the + * structure which holds it and a little more information, is never freed. + * This is because the temporary database is never actually closed out, + * so there is no safe/obvious place to free the whole thing. Also, + * every time an entry is added to the table, the key under which it is + * added is leaked. I think the only way to fix that is to provide our + * own allocation functions to PL_NewHashTable. But I'm not in the mood + * to do that, given that all of this is about to change, and we should + * have the key hash lookup done inside the permanent database itself. + * + * The database must be locked already. + */ +static SECStatus +InitDBspkDigestInfo(CERTCertDBHandle *handle) +{ + SPKDigestInfo *spkDigestInfo; + PLHashTable *table; + + PORT_Assert(handle != NULL); + PORT_Assert(handle->spkDigestInfo == NULL); + + spkDigestInfo = PORT_ZAlloc(sizeof(SPKDigestInfo)); + if ( spkDigestInfo == NULL ) { + return(SECFailure); + } + + table = PL_NewHashTable(128, PL_HashString, PL_CompareStrings, + (PLHashComparator) SECITEM_ItemsAreEqual, + NULL, NULL); + if ( table == NULL ) { + PORT_Free(spkDigestInfo); + return(SECFailure); + } + + spkDigestInfo->table = table; + handle->spkDigestInfo = spkDigestInfo; + return(SECSuccess); +} + +static SECHashObject * +OidTagToRawDigestObject(SECOidTag digestAlg) +{ + SECHashObject *rawDigestObject; + + switch (digestAlg) { + case SEC_OID_MD2: + rawDigestObject = &SECRawHashObjects[HASH_AlgMD2]; + break; + case SEC_OID_MD5: + rawDigestObject = &SECRawHashObjects[HASH_AlgMD5]; + break; + case SEC_OID_SHA1: + rawDigestObject = &SECRawHashObjects[HASH_AlgSHA1]; + break; + default: + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + rawDigestObject = NULL; + break; + } + return(rawDigestObject); +} + +/* + * Digest the cert's subject public key using the specified algorithm. + * The necessary storage for the digest data is allocated. If "fill" is + * non-null, the data is put there, otherwise a SECItem is allocated. + * Allocation from "arena" if it is non-null, heap otherwise. Any problem + * results in a NULL being returned (and an appropriate error set). + */ +SECItem * +CERT_SPKDigestValueForCert(PRArenaPool *arena, CERTCertificate *cert, + SECOidTag digestAlg, SECItem *fill) +{ + SECHashObject *digestObject; + void *digestContext; + SECItem *result = NULL; + void *mark = NULL; + SECItem spk; + + if ( arena != NULL ) { + mark = PORT_ArenaMark(arena); + } + + /* + * This can end up being called before PKCS #11 is initialized, + * so we have to use the raw digest functions. + */ + digestObject = OidTagToRawDigestObject(digestAlg); + if ( digestObject == NULL ) { + goto loser; + } + + result = SECITEM_AllocItem(arena, fill, digestObject->length); + if ( result == NULL ) { + goto loser; + } + + /* + * Copy just the length and data pointer (nothing needs to be freed) + * of the subject public key so we can convert the length from bits + * to bytes, which is what the digest function expects. + */ + spk = cert->subjectPublicKeyInfo.subjectPublicKey; + DER_ConvertBitString(&spk); + + /* + * Now digest the value, using the specified algorithm. + */ + digestContext = digestObject->create(); + if ( digestContext == NULL ) { + goto loser; + } + digestObject->begin(digestContext); + digestObject->update(digestContext, spk.data, spk.len); + digestObject->end(digestContext, result->data, &(result->len), result->len); + digestObject->destroy(digestContext, PR_TRUE); + + if ( arena != NULL ) { + PORT_ArenaUnmark(arena, mark); + } + return(result); + +loser: + if ( arena != NULL ) { + PORT_ArenaRelease(arena, mark); + } else { + if ( result != NULL ) { + SECITEM_FreeItem(result, (fill == NULL) ? PR_TRUE : PR_FALSE); + } + } + return(NULL); +} + +/* + * Return the index for the spk digest lookup table for "spkDigest". + * + * Caller is responsible for freeing the returned string. + */ +static char * +spkDigestIndexFromDigest(SECItem *spkDigest) +{ + return BTOA_ConvertItemToAscii(spkDigest); +} + +/* + * Return the index for the spk digest lookup table for this certificate, + * based on the specified digest algorithm. + * + * Caller is responsible for freeing the returned string. + */ +static char * +spkDigestIndexFromCert(CERTCertificate *cert, SECOidTag digestAlg) +{ + SECItem *spkDigest; + char *index; + + spkDigest = CERT_SPKDigestValueForCert(NULL, cert, digestAlg, NULL); + if ( spkDigest == NULL ) + return(NULL); + + index = spkDigestIndexFromDigest(spkDigest); + + SECITEM_FreeItem(spkDigest, PR_TRUE); + + return(index); +} + +/* + * Remove the spk digest for the given cert from the spk digest table, + * based on the given digest algorithm. + * + * The database must be locked already. + */ +static SECStatus +RemoveCertFromSPKDigestTableForAlg(CERTCertDBHandle *handle, + CERTCertificate *cert, SECOidTag digestAlg) +{ + SECStatus rv = SECSuccess; + SECItem *certDBKey = NULL; + char *index = NULL; + PLHashTable *table; + + /* Expect to only be called if there is a table to work with. */ + PORT_Assert(handle->spkDigestInfo != NULL); + + table = SPK_DIGEST_TABLE(handle); + PORT_Assert(table != NULL); + + index = spkDigestIndexFromCert(cert, digestAlg); + if ( index == NULL ) { + rv = SECFailure; + goto done; + } + + /* + * Get hold of this value so we can free it after doing the remove. + */ + certDBKey = PL_HashTableLookup(table, index); + if ( certDBKey == NULL ) { + /* not found means nothing to remove, which is fine */ + goto done; + } + + if ( PL_HashTableRemove(table, index) != PR_TRUE ) { + /* This should not actually happen, since we already did a lookup. */ + PORT_Assert(0); + rv = SECFailure; + } + +done: + if ( certDBKey != NULL ) { + SECITEM_FreeItem(certDBKey, PR_TRUE); + } + if ( index != NULL ) { + PORT_Free(index); + } + return(rv); +} + +/* + * Remove the spk digests for the given cert from the spk digest table, + * for all known digest algorithms. + * + * The database must be locked already. + */ +static SECStatus +RemoveCertFromSPKDigestTable(CERTCertDBHandle *handle, CERTCertificate *cert) +{ + /* + * If no certs have been added yet, then nothing to do. + */ + if ( handle->spkDigestInfo == NULL ) { + return(SECSuccess); + } + + (void) RemoveCertFromSPKDigestTableForAlg(handle, cert, SEC_OID_MD2); + (void) RemoveCertFromSPKDigestTableForAlg(handle, cert, SEC_OID_MD5); + return RemoveCertFromSPKDigestTableForAlg(handle, cert, SEC_OID_SHA1); +} + +/* + * Add the spk digest for the given cert to the spk digest table, + * based on the given digest algorithm. + * + * If a cert for the same spk digest is already in the table, choose whichever + * cert is "newer". (The other cert cannot be found via spk digest.) + * + * The database must be locked already. + * + * XXX Note that this implementation results in leaking the index value. + * Fixing that did not seem worth the trouble, given we will only leak + * once per cert. This whole thing should be done differently in the + * new rewrite (Stan), and then the problem will go away. + */ +static SECStatus +AddCertToSPKDigestTableForAlg(CERTCertDBHandle *handle, CERTCertificate *cert, + SECItem *certDBKey, SECOidTag digestAlg) +{ + SECStatus rv = SECFailure; + SECItem *oldCertDBKey = NULL; + PRBool addit = PR_TRUE; + CERTCertificate *oldCert = NULL; + char *index = NULL; + PLHashTable *table; + + /* + * After running some testing doing key hash lookups (like using OCSP), + * if these are never hit, they can probably be removed. + */ + PORT_Assert(handle != NULL); + PORT_Assert(handle == cert->dbhandle); + PORT_Assert(handle->spkDigestInfo != NULL); + PORT_Assert((certDBKey == &cert->certKey) + || (SECITEM_CompareItem(certDBKey, + &cert->certKey) == SECEqual)); + + table = SPK_DIGEST_TABLE(handle); + PORT_Assert(table != NULL); + + index = spkDigestIndexFromCert(cert, digestAlg); + if ( index == NULL ) { + goto loser; + } + + /* + * See if this cert's spk digest is already in the table. + */ + oldCertDBKey = PL_HashTableLookup(table, index); + if ( oldCertDBKey != NULL ) { + /* + * The spk digest *is* already in the table. We need to find that + * cert and see -- if it is the same, then we can just leave as is. + * Otherwise we have to choose which cert we want represented; + * in that case the best plan I can think of is to hang onto the + * most recent one. + */ + oldCert = CERT_FindCertByKey(handle, oldCertDBKey); + if ( oldCert != NULL ) { + if ( cert == oldCert ) { + /* They are the same cert, so we are done. */ + addit = PR_FALSE; + oldCertDBKey = NULL; /* don't want to free it */ + } else if ( CERT_IsNewer(cert, oldCert) ) { + if ( PL_HashTableRemove(table, index) != PR_TRUE ) { + goto loser; + } + } else { + /* oldCert is "newer", so we are done. */ + addit = PR_FALSE; + oldCertDBKey = NULL; /* don't want to free it */ + } + } + } + + if ( addit ) { + certDBKey = SECITEM_DupItem(certDBKey); + if ( certDBKey == NULL ) { + goto loser; + } + if ( PL_HashTableAdd(table, index, certDBKey) == NULL ) { + SECITEM_FreeItem(certDBKey, PR_TRUE); + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + index = NULL; /* don't want to free it */ + } + + rv = SECSuccess; + +loser: + if ( oldCertDBKey != NULL ) { + SECITEM_FreeItem(oldCertDBKey, PR_TRUE); + } + if ( index != NULL ) { + PORT_Free(index); + } + if ( oldCert != NULL ) { + CERT_DestroyCertificate(oldCert); + } + return(rv); +} + +/* + * Add the spk digest for the given cert to the spk digest table, + * for all known digest algorithms. + * + * The database must be locked already, and the digest table already created. + */ +static SECStatus +AddCertToSPKDigestTableForAllAlgs(CERTCertDBHandle *handle, + CERTCertificate *cert, SECItem *certDBKey) +{ + (void) AddCertToSPKDigestTableForAlg(handle, cert, certDBKey, SEC_OID_MD2); + (void) AddCertToSPKDigestTableForAlg(handle, cert, certDBKey, SEC_OID_MD5); + return AddCertToSPKDigestTableForAlg(handle, cert, certDBKey, SEC_OID_SHA1); +} + +/* + * Add the spk digests for the given cert to the spk digest table, + * for all known digest algorithms. (This function is called when a + * new cert is added to the temporary database.) + * + * If the spk digest table does not yet exist, create it. + * + * In an ideal world, we would not hardwire the digest algorithms. + * But it is the case that we do not currently support any digest + * algorithms outside of these three. In the newer, cleaned-up world, + * this may be done differently. + * + * The database must be locked already. + */ +static SECStatus +AddCertToSPKDigestTable(CERTCertDBHandle *handle, CERTCertificate *cert) +{ + SECStatus rv; + + if ( handle->spkDigestInfo == NULL ) { + rv = InitDBspkDigestInfo(handle); + if ( rv != SECSuccess ) { + return(rv); + } + } + + return AddCertToSPKDigestTableForAllAlgs(handle, cert, &cert->certKey); +} + +/* + * Add the spk digest for the given cert to the spk digest table, + * for all known digest algorithms. This function is called while + * traversing all of the certs in the permanent database -- since + * that imposes some constraints on its arguments this routine is a + * simple cover for the "real" interface. + * + * The database must be locked already, and the digest table already created. + */ +static SECStatus +AddCertToSPKDigestTableInTraversal(CERTCertificate *cert, SECItem *certDBKey, + void *data) +{ + CERTCertDBHandle *handle = data; + + return AddCertToSPKDigestTableForAllAlgs(handle, cert, certDBKey); +} + +/* + * Add the spk digests for the all permanent certs to the spk digest table, + * for all known digest algorithms. + * + * This locks the database, and then checks to make sure that the work + * actually needs to get done. + * + * If the spk digest table does not yet exist, it is created. + */ +static SECStatus +PopulateSPKDigestTable(CERTCertDBHandle *handle) +{ + SPKDigestInfo *spkDigestInfo; + SECStatus rv = SECSuccess; + + CERT_LockDB(handle); + + spkDigestInfo = handle->spkDigestInfo; + if ( spkDigestInfo == NULL ) { + rv = InitDBspkDigestInfo(handle); + if ( rv != SECSuccess ) { + return(rv); + } + spkDigestInfo = handle->spkDigestInfo; + PORT_Assert(spkDigestInfo != NULL); + } else { + /* + * Check to see if someone already did it; it is important to do + * this after getting the lock. + */ + if ( spkDigestInfo->permPopulated == PR_TRUE ) { + goto done; + } + } + + rv = TraversePermCertsNoLocking(handle, AddCertToSPKDigestTableInTraversal, + handle); + + if ( rv != SECSuccess ) { + goto done; + } + + spkDigestInfo->permPopulated = PR_TRUE; + +done: + CERT_UnlockDB(handle); + + return(rv); +} + +/* + * Lookup a certificate by a digest of a subject public key. If it is + * found, it is returned (and must then be destroyed by the caller). + * NULL is returned otherwise -- if there was a problem performing the + * lookup, an appropriate error is set (e.g. SEC_ERROR_NO_MEMORY); + * if the cert simply was not found, the error is SEC_ERROR_UNKNOWN_CERT. + * + * If the lookup table has not yet been created or populated, do that first. + */ +CERTCertificate * +CERT_FindCertBySPKDigest(CERTCertDBHandle *handle, SECItem *spkDigest) +{ + SPKDigestInfo *spkDigestInfo; + char *index = NULL; + SECItem *certDBKey; + CERTCertificate *cert = NULL; + + PORT_Assert(handle != NULL); + spkDigestInfo = handle->spkDigestInfo; + + if ( spkDigestInfo == NULL || spkDigestInfo->permPopulated != PR_TRUE ) { + if ( PopulateSPKDigestTable(handle) != SECSuccess ) { + goto loser; + } + } + + index = spkDigestIndexFromDigest(spkDigest); + if ( index == NULL ) { + goto loser; + } + + certDBKey = PL_HashTableLookup(SPK_DIGEST_TABLE(handle), index); + if ( certDBKey != NULL ) { + cert = CERT_FindCertByKey(handle, certDBKey); + } + + if ( cert == NULL ) { + PORT_SetError(SEC_ERROR_UNKNOWN_CERT); + } + +loser: + if ( index != NULL ) { + PORT_Free(index); + } + return(cert); +} + +static void +DestroyCertificate(CERTCertificate *cert, PRBool lockdb) +{ + int refCount; + CERTCertDBHandle *handle; + + if ( cert ) { + + handle = cert->dbhandle; + + /* + * handle may be NULL, for example if the cert was created with + * CERT_DecodeDERCertificate. + */ + if ( lockdb && handle ) { + CERT_LockDB(handle); + } + + CERT_LockCertRefCount(cert); + PORT_Assert(cert->referenceCount > 0); + refCount = --cert->referenceCount; + CERT_UnlockCertRefCount(cert); + + if ( ( refCount == 0 ) && !cert->keepSession ) { + certDBEntryCert *entry = cert->dbEntry; + PRArenaPool * arena = cert->arena; + + if ( cert->istemp ) { + CERT_DeleteTempCertificate(cert); + } + + if ( entry ) { + DestroyDBEntry((certDBEntry *)entry); + } + + /* zero cert before freeing. Any stale references to this cert + * after this point will probably cause an exception. */ + PORT_Memset(cert, 0, sizeof *cert); + + cert = NULL; + + /* free the arena that contains the cert. */ + PORT_FreeArena(arena, PR_FALSE); + } + if ( lockdb && handle ) { + CERT_UnlockDB(handle); + } + } + + return; +} + +void +CERT_DestroyCertificate(CERTCertificate *cert) +{ + DestroyCertificate(cert, PR_TRUE); + return; +} + +void +CERT_DestroyCertificateNoLocking(CERTCertificate *cert) +{ + DestroyCertificate(cert, PR_FALSE); + return; +} + +/* + * cache a CRL from the permanent database into the temporary database + */ +CERTSignedCrl * +SEC_AddPermCrlToTemp(CERTCertDBHandle *handle, certDBEntryRevocation *entry) +{ + CERTSignedCrl *crl = NULL; + DBT key; + DBT data; + int status; + PRArenaPool *arena = NULL; + SECItem keyitem; + SECStatus rv; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + crl = CERT_DecodeDERCrl(NULL, &entry->derCrl, + entry->common.type == certDBEntryTypeRevocation ? + SEC_CRL_TYPE : SEC_KRL_TYPE); + + if ( crl == NULL ) { + goto loser; + } + + /* only save pointer to cert in database */ + data.data = &crl; + data.size = sizeof(crl); + + + rv = EncodeDBGenericKey(&(crl->crl.derName), arena, + &keyitem, entry->common.type); + if ( rv != SECSuccess ) { + goto loser; + } + + if (entry->url) { + int nnlen = PORT_Strlen(entry->url) + 1; + crl->url = (char *)PORT_ArenaAlloc(crl->arena, nnlen); + if ( !crl->url ) { + goto loser; + } + PORT_Memcpy(crl->url, entry->url, nnlen); + } else { + crl->url = NULL; + } + + key.data = keyitem.data; + key.size = keyitem.len; + + /* enter into main db */ + status = certdb_Put(handle->tempCertDB, &key, &data, R_NOOVERWRITE); + if ( status ) { + goto loser; + } + + crl->istemp = PR_TRUE; + crl->isperm = PR_TRUE; + crl->dbhandle = handle; + crl->dbEntry = entry; + + + PORT_FreeArena(arena, PR_FALSE); + + crl->keep = PR_TRUE; + return(crl); + +loser: + if ( crl ) { + SEC_DestroyCrl(crl); + } + + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + PORT_SetError(SEC_ERROR_BAD_DATABASE); + return(0); +} + +SECStatus +SEC_DeleteTempCrl(CERTSignedCrl *crl) +{ + SECStatus rv; + DBT nameKey; + CERTCertDBHandle *handle; + SECItem keyitem; + PRArenaPool *arena; + int ret; + + handle = crl->dbhandle; + + if ( !crl->istemp ) { + return(SECSuccess); + } + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + rv = EncodeDBGenericKey + (&crl->crl.derName, arena, &keyitem, crl->dbEntry->common.type); + if ( rv != SECSuccess ) { + goto loser; + } + + nameKey.data = keyitem.data; + nameKey.size = keyitem.len; + + /* delete the cert */ + ret = certdb_Del(handle->tempCertDB, &nameKey, 0); + if ( ret ) { + goto loser; + } + + crl->istemp = PR_FALSE; + + PORT_FreeArena(arena, PR_FALSE); + return(SECSuccess); + +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(SECFailure); +} + +/* + * Lookup a CRL in the databases. We mirror the same fast caching data base + * caching stuff used by certificates....? + */ +CERTSignedCrl * +SEC_FindCrlByKey(CERTCertDBHandle *handle, SECItem *crlKey, int type) +{ + DBT tmpdata; + int ret; + SECItem keyitem; + DBT key; + SECStatus rv; + CERTSignedCrl *crl = NULL; + PRArenaPool *arena = NULL; + certDBEntryRevocation *entry; + certDBEntryType crlType = (type == SEC_CRL_TYPE) ? + certDBEntryTypeRevocation : certDBEntryTypeKeyRevocation; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + rv = EncodeDBGenericKey(crlKey, arena, &keyitem, crlType); + if ( rv != SECSuccess ) { + goto loser; + } + + key.data = keyitem.data; + key.size = keyitem.len; + + /* lookup in the temporary database */ + ret = certdb_Get( handle->tempCertDB, &key, &tmpdata, 0 ); + + /* error accessing the database */ + if ( ret < 0 ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + if ( ret == 0 ) { /* found in temp database */ + if ( tmpdata.size != sizeof(CERTSignedCrl *) ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + PORT_Memcpy(&crl, tmpdata.data, tmpdata.size); + crl->referenceCount++; + } else { /* not found in temporary database */ + + /* find in perm database */ + entry = ReadDBCrlEntry(handle, crlKey, crlType); + + if ( entry == NULL ) { + goto loser; + } + + crl = SEC_AddPermCrlToTemp(handle, entry); + } + +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + + return(crl); +} + + +CERTSignedCrl * +SEC_FindCrlByName(CERTCertDBHandle *handle, SECItem *crlKey, int type) +{ + return SEC_FindCrlByKey(handle,crlKey,type); +} + +CERTSignedCrl * +SEC_FindCrlByDERCert(CERTCertDBHandle *handle, SECItem *derCrl, int type) +{ + PRArenaPool *arena; + SECItem crlKey; + SECStatus rv; + CERTSignedCrl *crl = NULL; + + /* create a scratch arena */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + return(NULL); + } + + /* extract the database key from the cert */ + rv = CERT_KeyFromDERCrl(arena, derCrl, &crlKey); + if ( rv != SECSuccess ) { + goto loser; + } + + /* find the crl */ + crl = SEC_FindCrlByKey(handle, &crlKey, type); + +loser: + PORT_FreeArena(arena, PR_FALSE); + return(crl); +} + + +SECStatus +SEC_DestroyCrl(CERTSignedCrl *crl) +{ + if (crl) { + if (crl->referenceCount-- <= 1) { + if (!crl->keep) { + SEC_DeleteTempCrl(crl); + if (crl->dbEntry) { + DestroyDBEntry((certDBEntry *)crl->dbEntry); + } + PORT_FreeArena(crl->arena, PR_FALSE); + } + } + } + return SECSuccess; +} + +CERTSignedCrl * +cert_DBInsertCRL (CERTCertDBHandle *handle, char *url, + CERTSignedCrl *newCrl, SECItem *derCrl, int type) +{ + CERTSignedCrl *oldCrl = NULL, *crl = NULL; + certDBEntryRevocation *entry = NULL; + PRArenaPool *arena = NULL; + SECCertTimeValidity validity; + certDBEntryType crlType = (type == SEC_CRL_TYPE) ? + certDBEntryTypeRevocation : certDBEntryTypeKeyRevocation; + SECStatus rv; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) goto done; + + validity = SEC_CheckCrlTimes(&newCrl->crl,PR_Now()); + if ( validity == secCertTimeExpired) { + if (type == SEC_CRL_TYPE) { + PORT_SetError(SEC_ERROR_CRL_EXPIRED); + } else { + PORT_SetError(SEC_ERROR_KRL_EXPIRED); + } + goto done; + } else if (validity == secCertTimeNotValidYet) { + if (type == SEC_CRL_TYPE) { + PORT_SetError(SEC_ERROR_CRL_NOT_YET_VALID); + } else { + PORT_SetError(SEC_ERROR_KRL_NOT_YET_VALID); + } + goto done; + } + + oldCrl = SEC_FindCrlByKey(handle, &newCrl->crl.derName, type); + + /* if there is an old crl, make sure the one we are installing + * is newer. If not, exit out, otherwise delete the old crl. + */ + if (oldCrl != NULL) { + if (!SEC_CrlIsNewer(&newCrl->crl,&oldCrl->crl)) { + + if (type == SEC_CRL_TYPE) { + PORT_SetError(SEC_ERROR_OLD_CRL); + } else { + PORT_SetError(SEC_ERROR_OLD_KRL); + } + + goto done; + } + + if ((SECITEM_CompareItem(&newCrl->crl.derName, + &oldCrl->crl.derName) != SECEqual) && + (type == SEC_KRL_TYPE) ) { + + PORT_SetError(SEC_ERROR_CKL_CONFLICT); + goto done; + } + + /* if we have a url in the database, use that one */ + if (oldCrl->url) { + int nnlen = PORT_Strlen(oldCrl->url) + 1; + url = (char *)PORT_ArenaAlloc(arena, nnlen); + if ( url != NULL ) { + PORT_Memcpy(url, oldCrl->url, nnlen); + } + } + + + /* really destroy this crl */ + /* first drum it out of the permanment Data base */ + SEC_DeletePermCRL(oldCrl); + /* then get rid of our reference to it... */ + SEC_DestroyCrl(oldCrl); + oldCrl = NULL; + + } + + /* Write the new entry into the data base */ + entry = NewDBCrlEntry(derCrl, url, crlType, 0); + if (entry == NULL) goto done; + + rv = WriteDBCrlEntry(handle, entry); + if (rv != SECSuccess) goto done; + + crl = SEC_AddPermCrlToTemp(handle, entry); + if (crl) entry = NULL; /*crl->dbEntry now points to entry data */ + crl->isperm = PR_TRUE; + +done: + if (entry) DestroyDBEntry((certDBEntry *)entry); + if (arena) PORT_FreeArena(arena, PR_FALSE); + if (oldCrl) SEC_DestroyCrl(oldCrl); + + return crl; +} + + +/* + * create a new CRL from DER material. + * + * The signature on this CRL must be checked before you + * load it. ??? + */ +CERTSignedCrl * +SEC_NewCrl(CERTCertDBHandle *handle, char *url, SECItem *derCrl, int type) +{ + CERTSignedCrl *newCrl = NULL, *crl = NULL; + + /* make this decode dates! */ + newCrl = CERT_DecodeDERCrl(NULL, derCrl, type); + if (newCrl == NULL) { + if (type == SEC_CRL_TYPE) { + PORT_SetError(SEC_ERROR_CRL_INVALID); + } else { + PORT_SetError(SEC_ERROR_KRL_INVALID); + } + goto done; + } + + crl = cert_DBInsertCRL (handle, url, newCrl, derCrl, type); + + +done: + if (newCrl) PORT_FreeArena(newCrl->arena, PR_FALSE); + + return crl; +} + + +/* + * replace the existing URL in the data base with a new one + */ +SECStatus +SEC_CrlReplaceUrl(CERTSignedCrl *crl,char *url) { + SECStatus rv = SECFailure; + certDBEntryRevocation *entry = NULL; + int nnlen=0; + + SEC_DeletePermCRL(crl); + + /* Write the new entry into the data base */ + entry = NewDBCrlEntry(&crl->dbEntry->derCrl, url, crl->dbEntry->common.type, 0); + if (entry == NULL) goto done; + + rv = WriteDBCrlEntry(crl->dbhandle, entry); + if (rv != SECSuccess) goto done; + + if (url) { + nnlen = PORT_Strlen(url) + 1; + crl->url = (char *)PORT_ArenaAlloc(crl->arena, nnlen); + if ( !crl->url ) { + goto done; + } + PORT_Memcpy(crl->url, url, nnlen); + } else { + crl->url = NULL; + } +done: + return rv; +} + + +/* + * collect a linked list of CRL's + */ +static SECStatus CollectCrls(SECItem *dbdata, SECItem *dbkey, + certDBEntryType type, void *data) { + SECStatus rv; + certDBEntryRevocation entry; + SECItem entryitem; + PRArenaPool *arena = NULL; + CERTCrlHeadNode *head; + CERTCrlNode *new_node; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + goto loser; + } + + head = (CERTCrlHeadNode *)data; + entry.common.version = (unsigned int)dbdata->data[0]; + entry.common.type = (certDBEntryType)dbdata->data[1]; + entry.common.flags = (unsigned int)dbdata->data[2]; + entry.common.arena = arena; + + entryitem.len = dbdata->len - SEC_DB_ENTRY_HEADER_LEN; + entryitem.data = &dbdata->data[SEC_DB_ENTRY_HEADER_LEN]; + + rv = DecodeDBCrlEntry(&entry, &entryitem); + if (rv != SECSuccess ) { + goto loser; + } + + new_node = (CERTCrlNode *)PORT_ArenaAlloc(head->arena, sizeof(CERTCrlNode)); + if (new_node == NULL) { + goto loser; + } + + new_node->type = (entry.common.type == certDBEntryTypeRevocation) ? + SEC_CRL_TYPE : SEC_KRL_TYPE; + new_node->crl=CERT_DecodeDERCrl(head->arena,&entry.derCrl,new_node->type); + + if (entry.url) { + int nnlen = PORT_Strlen(entry.url) + 1; + new_node->crl->url = (char *)PORT_ArenaAlloc(head->arena, nnlen); + if ( !new_node->crl->url ) { + goto loser; + } + PORT_Memcpy(new_node->crl->url, entry.url, nnlen); + } else { + new_node->crl->url = NULL; + } + + + new_node->next = NULL; + if (head->last) { + head->last->next = new_node; + head->last = new_node; + } else { + head->first = head->last = new_node; + } + return (SECSuccess); + +loser: + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + } + return(SECFailure); +} + + +SECStatus +SEC_LookupCrls(CERTCertDBHandle *handle, CERTCrlHeadNode **nodes, int type) +{ + CERTCrlHeadNode *head; + PRArenaPool *arena = NULL; + SECStatus rv; + + *nodes = NULL; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + return SECFailure; + } + + /* build a head structure */ + head = (CERTCrlHeadNode *)PORT_ArenaAlloc(arena, sizeof(CERTCrlHeadNode)); + head->arena = arena; + head->first = NULL; + head->last = NULL; + head->dbhandle = handle; + + /* Look up the proper crl types */ + *nodes = head; + + CERT_LockDB(handle); + + switch (type) { + case SEC_CRL_TYPE: + rv = SEC_TraverseDBEntries(handle, certDBEntryTypeRevocation, + CollectCrls, (void *)head); + break; + case SEC_KRL_TYPE: + rv = SEC_TraverseDBEntries(handle, certDBEntryTypeKeyRevocation, + CollectCrls, (void *)head); + break; + case -1: + rv = SEC_TraverseDBEntries(handle, certDBEntryTypeKeyRevocation, + CollectCrls, (void *)head); + if (rv != SECSuccess) break; + rv = SEC_TraverseDBEntries(handle, certDBEntryTypeRevocation, + CollectCrls, (void *)head); + break; + default: + rv = SECFailure; + break; + } + + CERT_UnlockDB(handle); + + if (rv != SECSuccess) { + if ( arena ) { + PORT_FreeArena(arena, PR_FALSE); + *nodes = NULL; + } + } + + return rv; +} + + +SECStatus +SEC_DeletePermCRL(CERTSignedCrl *crl) { + SECStatus rv; + + if (crl == NULL) { + return SECFailure; + } + + rv = DeleteDBCrlEntry(crl->dbhandle, &crl->crl.derName, + crl->dbEntry->common.type); + if (rv != SECSuccess) goto done; + + /* now force it to be freed when all the reference counts go */ + crl->keep = PR_FALSE; + /* force it out of the temporary data base */ + SEC_DeleteTempCrl(crl); + +done: + return rv; +} + +/* + * find a cert by email address + * + * pick one that is a valid recipient, meaning that it is an encryption + * cert. + * + */ +static CERTCertificate* +find_smime_recipient_cert(CERTCertDBHandle* handle, const char* email_addr) +{ + CERTCertificate* cert = NULL; + CERTCertList* certList = NULL; + SECStatus rv; + + certList = CERT_CreateEmailAddrCertList(NULL, handle, (char*)email_addr, + PR_Now(), PR_TRUE); + if (certList == NULL) { + return NULL; + } + + rv = CERT_FilterCertListByUsage(certList, certUsageEmailRecipient, + PR_FALSE); + + if (!CERT_LIST_END(CERT_LIST_HEAD(certList), certList)) { + cert = CERT_DupCertificate(CERT_LIST_HEAD(certList)->cert); + } + + CERT_DestroyCertList(certList); + + return cert; /* cert may point to a cert or may be NULL */ +} + +/* + * This function has the logic that decides if another person's cert and + * email profile from an S/MIME message should be saved. It can deal with + * the case when there is no profile. + */ +SECStatus +CERT_SaveSMimeProfile(CERTCertificate *cert, SECItem *emailProfile, + SECItem *profileTime) +{ + certDBEntrySMime *entry = NULL, *oldentry = NULL; + int64 oldtime; + int64 newtime; + SECStatus rv; + CERTCertificate *oldcert = NULL; + PRBool saveit; + CERTCertTrust trust; + CERTCertTrust tmptrust; + char *emailAddr; + + emailAddr = cert->emailAddr; + + PORT_Assert(emailAddr); + if ( emailAddr == NULL ) { + goto loser; + } + + saveit = PR_FALSE; + + oldcert = find_smime_recipient_cert(cert->dbhandle, emailAddr); + if (oldcert) { + /* see if there is an entry already */ + oldentry = ReadDBSMimeEntry(cert->dbhandle, emailAddr); + } + + /* both profileTime and emailProfile have to exist or not exist */ + if ( emailProfile == NULL ) { + profileTime = NULL; + } else if ( profileTime == NULL ) { + emailProfile = NULL; + } + + if ( oldentry == NULL ) { + /* no old entry for this address */ + PORT_Assert(oldcert == NULL); + saveit = PR_TRUE; + } else { + /* there was already a profile for this email addr */ + if ( profileTime ) { + /* we have an old and new profile - save whichever is more recent*/ + if ( oldentry->optionsDate.len == 0 ) { + /* always replace if old entry doesn't have a time */ + oldtime = LL_MININT; + } else { + rv = DER_UTCTimeToTime(&oldtime, &oldentry->optionsDate); + if ( rv != SECSuccess ) { + goto loser; + } + } + + rv = DER_UTCTimeToTime(&newtime, profileTime); + if ( rv != SECSuccess ) { + goto loser; + } + + if ( LL_CMP(newtime, >, oldtime ) ) { + /* this is a newer profile, save it and cert */ + saveit = PR_TRUE; + } + } else { + /* we don't have a new profile or time */ + if ( oldentry->optionsDate.len == 0 ) { + /* the old entry doesn't have a time either, so compare certs*/ + if ( CERT_IsNewer(cert, oldcert) ) { + /* new cert is newer, use it instead */ + saveit = PR_TRUE; + } + } else { + if (oldcert) { + if (CERT_IsNewer(cert, oldcert)) { + saveit = PR_TRUE; + } + } else { + saveit = PR_TRUE; + } + } + } + } + + if ( saveit ) { + if ( oldcert && ( oldcert != cert ) ) { + /* old cert is different from new cert */ + if ( PORT_Memcmp(oldcert->trust, &trust, sizeof(trust)) == 0 ) { + /* old cert is only for e-mail, so delete it */ + SEC_DeletePermCertificate(oldcert); + } else { + /* old cert is for other things too, so just change trust */ + tmptrust = *oldcert->trust; + tmptrust.emailFlags &= ( ~CERTDB_VALID_PEER ); + rv = CERT_ChangeCertTrust(oldcert->dbhandle, oldcert, + &tmptrust); + if ( rv != SECSuccess ) { + goto loser; + } + } + } + +/* Subroutine */ + /* now save the entry */ + entry = NewDBSMimeEntry(emailAddr, &cert->derSubject, emailProfile, + profileTime, 0); + if ( entry == NULL ) { + goto loser; + } + + CERT_LockDB(cert->dbhandle); + + rv = DeleteDBSMimeEntry(cert->dbhandle, emailAddr); + /* if delete fails, try to write new entry anyway... */ + + rv = WriteDBSMimeEntry(cert->dbhandle, entry); + if ( rv != SECSuccess ) { + CERT_UnlockDB(cert->dbhandle); + goto loser; + } + + /* link subject entry back here */ + rv = UpdateSubjectWithEmailAddr(cert, emailAddr); + if ( rv != SECSuccess ) { + CERT_UnlockDB(cert->dbhandle); + goto loser; + } + + CERT_UnlockDB(cert->dbhandle); +/* End Subroutine */ + } + + rv = SECSuccess; + goto done; + +loser: + rv = SECFailure; + +done: + if ( oldcert ) { + CERT_DestroyCertificate(oldcert); + } + + if ( entry ) { + DestroyDBEntry((certDBEntry *)entry); + } + if ( oldentry ) { + DestroyDBEntry((certDBEntry *)oldentry); + } + + return(rv); +} + +CERTCertificate * +CERT_FindCertByEmailAddr(CERTCertDBHandle *handle, char *emailAddr) +{ + certDBEntrySMime *entry; + CERTCertificate *cert = NULL; + + emailAddr = CERT_FixupEmailAddr(emailAddr); + if ( emailAddr == NULL ) { + return(NULL); + } + + entry = ReadDBSMimeEntry(handle, emailAddr); + + /* XXX - this will have to change when multiple certs per subject + * are allowed + */ + if ( entry != NULL ) { + cert = CERT_FindCertByName(handle, &entry->subjectName); + } + + if ( entry ) { + DestroyDBEntry((certDBEntry *)entry); + } + + PORT_Free(emailAddr); + + return(cert); +} + +/* + * find the smime symmetric capabilities profile for a given cert + */ +SECItem * +CERT_FindSMimeProfile(CERTCertificate *cert) +{ + certDBEntrySMime *entry; + SECItem *retitem = NULL; + + PORT_Assert(cert->emailAddr != NULL); + + if ( cert->emailAddr == NULL ) { + return(NULL); + } + + entry = ReadDBSMimeEntry(cert->dbhandle, cert->emailAddr); + + if ( entry ) { + retitem = SECITEM_DupItem(&entry->smimeOptions); + DestroyDBEntry((certDBEntry *)entry); + } + + return(retitem); +} + +CERTCertificate * +CERT_FindCertByNicknameOrEmailAddr(CERTCertDBHandle *handle, char *name) +{ + CERTCertificate *cert; + cert = CERT_FindCertByNickname(handle, name); + if ( cert == NULL ) { + cert = CERT_FindCertByEmailAddr(handle, name); + } + + return(cert); +} + +PRBool +CERT_IsCertRevoked(CERTCertificate *cert) +{ + return(PR_FALSE); +} + +CERTCertificate * +CERT_NextSubjectCert(CERTCertificate *cert) +{ + CERTSubjectNode *node; + CERTCertificate *retcert = NULL; + + CERT_LockDB(cert->dbhandle); + + node = FindCertSubjectNode(cert); + PORT_Assert(node != NULL); + + if ( node->next != NULL ) { + retcert = CERT_FindCertByKeyNoLocking(cert->dbhandle, + &node->next->certKey); + } + + CERT_UnlockDB(cert->dbhandle); + + return(retcert); +} + +CERTCertificate * +CERT_PrevSubjectCert(CERTCertificate *cert) +{ + CERTSubjectNode *node; + CERTCertificate *retcert = NULL; + + CERT_LockDB(cert->dbhandle); + node = FindCertSubjectNode(cert); + PORT_Assert(node != NULL); + + if ( node->prev != NULL ) { + retcert = CERT_FindCertByKeyNoLocking(cert->dbhandle, + &node->prev->certKey); + } + CERT_UnlockDB(cert->dbhandle); + + return(retcert); +} + +SECStatus +CERT_SaveImportedCert(CERTCertificate *cert, SECCertUsage usage, + PRBool caOnly, char *nickname) +{ + SECStatus rv; + PRBool saveit; + CERTCertTrust trust; + CERTCertTrust tmptrust; + PRBool isCA; + unsigned int certtype; + PRBool freeNickname = PR_FALSE; + + isCA = CERT_IsCACert(cert, NULL); + if ( caOnly && ( !isCA ) ) { + return(SECSuccess); + } + + saveit = PR_TRUE; + + PORT_Memset((void *)&trust, 0, sizeof(trust)); + + certtype = cert->nsCertType; + + /* if no CA bits in cert type, then set all CA bits */ + if ( isCA && ( ! ( certtype & NS_CERT_TYPE_CA ) ) ) { + certtype |= NS_CERT_TYPE_CA; + } + + /* if no app bits in cert type, then set all app bits */ + if ( ( !isCA ) && ( ! ( certtype & NS_CERT_TYPE_APP ) ) ) { + certtype |= NS_CERT_TYPE_APP; + } + + if ( isCA && !nickname ) { + nickname = CERT_MakeCANickname(cert); + if ( nickname != NULL ) { + freeNickname = PR_TRUE; + } + } + + switch ( usage ) { + case certUsageEmailSigner: + case certUsageEmailRecipient: + if ( isCA ) { + if ( certtype & NS_CERT_TYPE_EMAIL_CA ) { + trust.emailFlags = CERTDB_VALID_CA; + } + } else { + PORT_Assert(nickname == NULL); + + if ( cert->emailAddr == NULL ) { + saveit = PR_FALSE; + } + + if ( certtype & NS_CERT_TYPE_EMAIL ) { + trust.emailFlags = CERTDB_VALID_PEER; + if ( ! ( cert->rawKeyUsage & KU_KEY_ENCIPHERMENT ) ) { + /* don't save it if KeyEncipherment is not allowed */ + saveit = PR_FALSE; + } + } + } + break; + case certUsageUserCertImport: + if ( isCA ) { + if ( certtype & NS_CERT_TYPE_SSL_CA ) { + trust.sslFlags = CERTDB_VALID_CA; + } + + if ( certtype & NS_CERT_TYPE_EMAIL_CA ) { + trust.emailFlags = CERTDB_VALID_CA; + } + + if ( certtype & NS_CERT_TYPE_OBJECT_SIGNING_CA ) { + trust.objectSigningFlags = CERTDB_VALID_CA; + } + + } else { + if ( certtype & NS_CERT_TYPE_SSL_CLIENT ) { + trust.sslFlags = CERTDB_VALID_PEER; + } + + if ( certtype & NS_CERT_TYPE_EMAIL ) { + trust.emailFlags = CERTDB_VALID_PEER; + } + + if ( certtype & NS_CERT_TYPE_OBJECT_SIGNING ) { + trust.objectSigningFlags = CERTDB_VALID_PEER; + } + } + break; + default: /* XXX added to quiet warnings; no other cases needed? */ + break; + } + + if ( (trust.sslFlags | trust.emailFlags | trust.objectSigningFlags) == 0 ){ + saveit = PR_FALSE; + } + + if ( saveit ) { + if ( cert->isperm ) { + /* Cert already in the DB. Just adjust flags */ + tmptrust = *cert->trust; + tmptrust.sslFlags |= trust.sslFlags; + tmptrust.emailFlags |= trust.emailFlags; + tmptrust.objectSigningFlags |= trust.objectSigningFlags; + + rv = CERT_ChangeCertTrust(cert->dbhandle, cert, + &tmptrust); + if ( rv != SECSuccess ) { + goto loser; + } + } else { + /* Cert not already in the DB. Add it */ + rv = CERT_AddTempCertToPerm(cert, nickname, &trust); + if ( rv != SECSuccess ) { + goto loser; + } + } + } + + rv = SECSuccess; + goto done; + +loser: + rv = SECFailure; +done: + + if ( freeNickname ) { + PORT_Free(nickname); + } + + return(rv); +} + +SECStatus +CERT_ChangeCertTrustByUsage(CERTCertDBHandle *certdb, + CERTCertificate *cert, SECCertUsage usage) +{ + SECStatus rv; + CERTCertTrust trust; + CERTCertTrust tmptrust; + unsigned int certtype; + PRBool saveit; + + saveit = PR_TRUE; + + PORT_Memset((void *)&trust, 0, sizeof(trust)); + + certtype = cert->nsCertType; + + /* if no app bits in cert type, then set all app bits */ + if ( ! ( certtype & NS_CERT_TYPE_APP ) ) { + certtype |= NS_CERT_TYPE_APP; + } + + switch ( usage ) { + case certUsageEmailSigner: + case certUsageEmailRecipient: + if ( certtype & NS_CERT_TYPE_EMAIL ) { + trust.emailFlags = CERTDB_VALID_PEER; + if ( ! ( cert->rawKeyUsage & KU_KEY_ENCIPHERMENT ) ) { + /* don't save it if KeyEncipherment is not allowed */ + saveit = PR_FALSE; + } + } + break; + case certUsageUserCertImport: + if ( certtype & NS_CERT_TYPE_EMAIL ) { + trust.emailFlags = CERTDB_VALID_PEER; + } + /* VALID_USER is already set if the cert was imported, + * in the case that the cert was already in the database + * through SMIME or other means, we should set the USER + * flags, if they are not already set. + */ + if( cert->isperm ) { + if ( certtype & NS_CERT_TYPE_SSL_CLIENT ) { + if( !(cert->trust->sslFlags & CERTDB_USER) ) { + trust.sslFlags |= CERTDB_USER; + } + } + + if ( certtype & NS_CERT_TYPE_EMAIL ) { + if( !(cert->trust->emailFlags & CERTDB_USER) ) { + trust.emailFlags |= CERTDB_USER; + } + } + + if ( certtype & NS_CERT_TYPE_OBJECT_SIGNING ) { + if( !(cert->trust->objectSigningFlags & CERTDB_USER) ) { + trust.objectSigningFlags |= CERTDB_USER; + } + } + } + break; + default: /* XXX added to quiet warnings; no other cases needed? */ + break; + } + + if ( (trust.sslFlags | trust.emailFlags | trust.objectSigningFlags) == 0 ){ + saveit = PR_FALSE; + } + + if ( saveit && cert->isperm ) { + /* Cert already in the DB. Just adjust flags */ + tmptrust = *cert->trust; + tmptrust.sslFlags |= trust.sslFlags; + tmptrust.emailFlags |= trust.emailFlags; + tmptrust.objectSigningFlags |= trust.objectSigningFlags; + + rv = CERT_ChangeCertTrust(cert->dbhandle, cert, + &tmptrust); + if ( rv != SECSuccess ) { + goto loser; + } + } + + rv = SECSuccess; + goto done; + +loser: + rv = SECFailure; +done: + + return(rv); +} + +int +CERT_GetDBContentVersion(CERTCertDBHandle *handle) +{ + certDBEntryContentVersion *entry; + int ret; + + entry = ReadDBContentVersionEntry(handle); + + if ( entry == NULL ) { + return(0); + } + + ret = entry->contentVersion; + + DestroyDBEntry((certDBEntry *)entry); + + return(ret); +} + +void +CERT_SetDBContentVersion(int version, CERTCertDBHandle *handle) +{ + SECStatus rv; + certDBEntryContentVersion *entry; + + entry = NewDBContentVersionEntry(0); + + if ( entry == NULL ) { + return; + } + + rv = DeleteDBContentVersionEntry(handle); + rv = WriteDBContentVersionEntry(handle, entry); + + DestroyDBEntry((certDBEntry *)entry); + + return; +} + +/* + * Creates or adds to a list of all certs with a give subject name, sorted by + * validity time, newest first. Invalid certs are considered older than + * valid certs. If validOnly is set, do not include invalid certs on list. + */ +CERTCertList * +CERT_CreateSubjectCertList(CERTCertList *certList, CERTCertDBHandle *handle, + SECItem *name, int64 sorttime, PRBool validOnly) +{ + CERTCertificate *cert = NULL; + CERTSubjectList *subjectList = NULL; + CERTSubjectNode *node; + SECStatus rv; + + if ( certList == NULL ) { + certList = CERT_NewCertList(); + } + + if ( certList == NULL ) { + goto loser; + } + + subjectList = FindSubjectList(handle, name, PR_FALSE); + + if ( subjectList != NULL ) { + node = subjectList->head; + PORT_Assert(node); + while (node) { + cert = CERT_FindCertByKey(handle, &node->certKey); + + /* if validOnly, then check validity period before adding to list*/ + if ( ( !validOnly ) || + ( CERT_CheckCertValidTimes(cert, sorttime, PR_FALSE) + == secCertTimeValid ) ) { + rv = CERT_AddCertToListSorted(certList, cert, + CERT_SortCBValidity, + (void *)&sorttime); + if ( rv != SECSuccess ) { + CERT_DestroyCertificate(cert); + goto loser; + } + } else { + CERT_DestroyCertificate(cert); + } + + node = node->next; + } + } + + return(certList); + +loser: + if ( certList != NULL ) { + CERT_DestroyCertList(certList); + } + + return(NULL); +} + +/* + * Creates or adds to a list of all certs with a give nickname, sorted by + * validity time, newest first. Invalid certs are considered older than valid + * certs. If validOnly is set, do not include invalid certs on list. + */ +CERTCertList * +CERT_CreateNicknameCertList(CERTCertList *certList, CERTCertDBHandle *handle, + char *nickname, int64 sorttime, PRBool validOnly) +{ + CERTCertificate *cert; + CERTCertList *ret; + + cert = CERT_FindCertByNickname(handle, nickname); + if ( cert == NULL ) { + return(NULL); + } + + ret = CERT_CreateSubjectCertList(certList, handle, &cert->derSubject, + sorttime, validOnly); + + CERT_DestroyCertificate(cert); + + return(ret); +} + +/* + * Creates or adds to a list of all certs with a give email addr, sorted by + * validity time, newest first. Invalid certs are considered older than valid + * certs. If validOnly is set, do not include invalid certs on list. + */ +CERTCertList * +CERT_CreateEmailAddrCertList(CERTCertList *certList, CERTCertDBHandle *handle, + char *emailAddr, int64 sorttime, PRBool validOnly) +{ + CERTCertificate *cert; + CERTCertList *ret; + + cert = CERT_FindCertByEmailAddr(handle, emailAddr); + if ( cert == NULL ) { + return(NULL); + } + + ret = CERT_CreateSubjectCertList(certList, handle, &cert->derSubject, + sorttime, validOnly); + + CERT_DestroyCertificate(cert); + + return(ret); +} |