diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /security/nss/lib/softoken/legacydb/pcertdb.c | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/nss/lib/softoken/legacydb/pcertdb.c')
-rw-r--r-- | security/nss/lib/softoken/legacydb/pcertdb.c | 5350 |
1 files changed, 5350 insertions, 0 deletions
diff --git a/security/nss/lib/softoken/legacydb/pcertdb.c b/security/nss/lib/softoken/legacydb/pcertdb.c new file mode 100644 index 0000000000..488d0f06ca --- /dev/null +++ b/security/nss/lib/softoken/legacydb/pcertdb.c @@ -0,0 +1,5350 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Permanent Certificate database handling code + */ +#include "lowkeyti.h" +#include "pcert.h" +#include "mcom_db.h" +#include "pcert.h" +#include "secitem.h" +#include "secder.h" + +#include "secerr.h" +#include "lgdb.h" + +/* forward declaration */ +NSSLOWCERTCertificate * +nsslowcert_FindCertByDERCertNoLocking(NSSLOWCERTCertDBHandle *handle, SECItem *derCert); +static SECStatus +nsslowcert_UpdateSMimeProfile(NSSLOWCERTCertDBHandle *dbhandle, + char *emailAddr, SECItem *derSubject, SECItem *emailProfile, + SECItem *profileTime); +static SECStatus +nsslowcert_UpdatePermCert(NSSLOWCERTCertDBHandle *dbhandle, + NSSLOWCERTCertificate *cert, char *nickname, NSSLOWCERTCertTrust *trust); +static SECStatus +nsslowcert_UpdateCrl(NSSLOWCERTCertDBHandle *handle, SECItem *derCrl, + SECItem *crlKey, char *url, PRBool isKRL); + +static NSSLOWCERTCertificate *certListHead = NULL; +static NSSLOWCERTTrust *trustListHead = NULL; +static certDBEntryCert *entryListHead = NULL; +static int certListCount = 0; +static int trustListCount = 0; +static int entryListCount = 0; +#define MAX_CERT_LIST_COUNT 10 +#define MAX_TRUST_LIST_COUNT 10 +#define MAX_ENTRY_LIST_COUNT 10 + +/* + * the following functions are wrappers for the db library that implement + * a global lock to make the database thread safe. + */ +static PZLock *dbLock = NULL; +static PZLock *certRefCountLock = NULL; +static PZLock *certTrustLock = NULL; +static PZLock *freeListLock = NULL; + +void +certdb_InitDBLock(NSSLOWCERTCertDBHandle *handle) +{ + if (dbLock == NULL) { + dbLock = PZ_NewLock(nssILockCertDB); + PORT_Assert(dbLock != NULL); + } +} + +SECStatus +nsslowcert_InitLocks(void) +{ + if (freeListLock == NULL) { + freeListLock = PZ_NewLock(nssILockRefLock); + if (freeListLock == NULL) { + return SECFailure; + } + } + if (certRefCountLock == NULL) { + certRefCountLock = PZ_NewLock(nssILockRefLock); + if (certRefCountLock == NULL) { + return SECFailure; + } + } + if (certTrustLock == NULL) { + certTrustLock = PZ_NewLock(nssILockCertDB); + if (certTrustLock == NULL) { + return SECFailure; + } + } + + return SECSuccess; +} + +/* + * Acquire the global lock on the cert database. + * This lock is currently used for the following operations: + * adding or deleting a cert to either the temp or perm databases + * converting a temp to perm or perm to temp + * changing (maybe just adding!?) the trust of a cert + * chaning the DB status checking Configuration + */ +static void +nsslowcert_LockDB(NSSLOWCERTCertDBHandle *handle) +{ + PZ_EnterMonitor(handle->dbMon); + return; +} + +/* + * Free the global cert database lock. + */ +static void +nsslowcert_UnlockDB(NSSLOWCERTCertDBHandle *handle) +{ +#ifdef DEBUG + PRStatus prstat = PZ_ExitMonitor(handle->dbMon); + PORT_Assert(prstat == PR_SUCCESS); +#else + PZ_ExitMonitor(handle->dbMon); +#endif +} + +/* + * Acquire the cert reference count lock + * There is currently one global lock for all certs, but I'm putting a cert + * arg here so that it will be easy to make it per-cert in the future if + * that turns out to be necessary. + */ +static void +nsslowcert_LockCertRefCount(NSSLOWCERTCertificate *cert) +{ + PORT_Assert(certRefCountLock != NULL); + + PZ_Lock(certRefCountLock); + return; +} + +/* + * Free the cert reference count lock + */ +static void +nsslowcert_UnlockCertRefCount(NSSLOWCERTCertificate *cert) +{ + PORT_Assert(certRefCountLock != NULL); + +#ifdef DEBUG + { + PRStatus prstat = PZ_Unlock(certRefCountLock); + PORT_Assert(prstat == PR_SUCCESS); + } +#else + PZ_Unlock(certRefCountLock); +#endif +} + +/* + * Acquire the cert trust lock + * There is currently one global lock for all certs, but I'm putting a cert + * arg here so that it will be easy to make it per-cert in the future if + * that turns out to be necessary. + */ +static void +nsslowcert_LockCertTrust(NSSLOWCERTCertificate *cert) +{ + PORT_Assert(certTrustLock != NULL); + + PZ_Lock(certTrustLock); + return; +} + +/* + * Free the cert trust lock + */ +static void +nsslowcert_UnlockCertTrust(NSSLOWCERTCertificate *cert) +{ + PORT_Assert(certTrustLock != NULL); + +#ifdef DEBUG + { + PRStatus prstat = PZ_Unlock(certTrustLock); + PORT_Assert(prstat == PR_SUCCESS); + } +#else + PZ_Unlock(certTrustLock); +#endif +} + +/* + * Acquire the cert reference count lock + * There is currently one global lock for all certs, but I'm putting a cert + * arg here so that it will be easy to make it per-cert in the future if + * that turns out to be necessary. + */ +static void +nsslowcert_LockFreeList(void) +{ + PORT_Assert(freeListLock != NULL); + + SKIP_AFTER_FORK(PZ_Lock(freeListLock)); + return; +} + +/* + * Free the cert reference count lock + */ +static void +nsslowcert_UnlockFreeList(void) +{ + PORT_Assert(freeListLock != NULL); + +#ifdef DEBUG + { + PRStatus prstat = PR_SUCCESS; + SKIP_AFTER_FORK(prstat = PZ_Unlock(freeListLock)); + PORT_Assert(prstat == PR_SUCCESS); + } +#else + SKIP_AFTER_FORK(PZ_Unlock(freeListLock)); +#endif +} + +NSSLOWCERTCertificate * +nsslowcert_DupCertificate(NSSLOWCERTCertificate *c) +{ + if (c) { + nsslowcert_LockCertRefCount(c); + ++c->referenceCount; + nsslowcert_UnlockCertRefCount(c); + } + return c; +} + +static int +certdb_Get(DB *db, DBT *key, DBT *data, unsigned int flags) +{ + int ret; + + PORT_Assert(dbLock != NULL); + PZ_Lock(dbLock); + + ret = (*db->get)(db, key, data, flags); + + (void)PZ_Unlock(dbLock); + + return (ret); +} + +static int +certdb_Put(DB *db, DBT *key, DBT *data, unsigned int flags) +{ + int ret = 0; + + PORT_Assert(dbLock != NULL); + PZ_Lock(dbLock); + + ret = (*db->put)(db, key, data, flags); + + (void)PZ_Unlock(dbLock); + + return (ret); +} + +static int +certdb_Sync(DB *db, unsigned int flags) +{ + int ret; + + PORT_Assert(dbLock != NULL); + PZ_Lock(dbLock); + + ret = (*db->sync)(db, flags); + + (void)PZ_Unlock(dbLock); + + return (ret); +} + +#define DB_NOT_FOUND -30991 /* from DBM 3.2 */ +static int +certdb_Del(DB *db, DBT *key, unsigned int flags) +{ + int ret; + + PORT_Assert(dbLock != NULL); + PZ_Lock(dbLock); + + ret = (*db->del)(db, key, flags); + + (void)PZ_Unlock(dbLock); + + /* don't fail if the record is already deleted */ + if (ret == DB_NOT_FOUND) { + ret = 0; + } + + return (ret); +} + +static int +certdb_Seq(DB *db, DBT *key, DBT *data, unsigned int flags) +{ + int ret; + + PORT_Assert(dbLock != NULL); + PZ_Lock(dbLock); + + ret = (*db->seq)(db, key, data, flags); + + (void)PZ_Unlock(dbLock); + + return (ret); +} + +static void +certdb_Close(DB *db) +{ + PORT_Assert(dbLock != NULL); + SKIP_AFTER_FORK(PZ_Lock(dbLock)); + + (*db->close)(db); + + SKIP_AFTER_FORK(PZ_Unlock(dbLock)); + + return; +} + +void +pkcs11_freeNickname(char *nickname, char *space) +{ + if (nickname && nickname != space) { + PORT_Free(nickname); + } +} + +char * +pkcs11_copyNickname(char *nickname, char *space, int spaceLen) +{ + int len; + char *copy = NULL; + + len = PORT_Strlen(nickname) + 1; + if (len <= spaceLen) { + copy = space; + PORT_Memcpy(copy, nickname, len); + } else { + copy = PORT_Strdup(nickname); + } + + return copy; +} + +void +pkcs11_freeStaticData(unsigned char *data, unsigned char *space) +{ + if (data && data != space) { + PORT_Free(data); + } +} + +unsigned char * +pkcs11_allocStaticData(int len, unsigned char *space, int spaceLen) +{ + unsigned char *data = NULL; + + if (len <= spaceLen) { + data = space; + } else { + data = (unsigned char *)PORT_Alloc(len); + } + + return data; +} + +unsigned char * +pkcs11_copyStaticData(unsigned char *data, int len, + unsigned char *space, int spaceLen) +{ + unsigned char *copy = pkcs11_allocStaticData(len, space, spaceLen); + if (copy) { + PORT_Memcpy(copy, data, len); + } + + return copy; +} + +/* + * destroy a database entry + */ +static void +DestroyDBEntry(certDBEntry *entry) +{ + PLArenaPool *arena = entry->common.arena; + + /* must be one of our certDBEntry from the free list */ + if (arena == NULL) { + certDBEntryCert *certEntry; + if (entry->common.type != certDBEntryTypeCert) { + return; + } + certEntry = (certDBEntryCert *)entry; + + pkcs11_freeStaticData(certEntry->derCert.data, certEntry->derCertSpace); + pkcs11_freeNickname(certEntry->nickname, certEntry->nicknameSpace); + + nsslowcert_LockFreeList(); + if (entryListCount > MAX_ENTRY_LIST_COUNT) { + PORT_Free(certEntry); + } else { + entryListCount++; + PORT_Memset(certEntry, 0, sizeof(*certEntry)); + certEntry->next = entryListHead; + entryListHead = certEntry; + } + nsslowcert_UnlockFreeList(); + return; + } + + /* 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; +} + +/* forward references */ +static void nsslowcert_DestroyCertificateNoLocking(NSSLOWCERTCertificate *cert); + +static SECStatus +DeleteDBEntry(NSSLOWCERTCertDBHandle *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(NSSLOWCERTCertDBHandle *handle, certDBEntryCommon *entry, + SECItem *dbkey, SECItem *dbentry, PLArenaPool *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; + /* version 7 has the same schema, we may be using a v7 db if we openned + * the databases readonly. */ + if (!((buf[0] == (unsigned char)CERT_DB_FILE_VERSION) || + (buf[0] == (unsigned char)CERT_DB_V7_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) { + if (arena) { + 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 = &buf[SEC_DB_ENTRY_HEADER_LEN]; + } + } else { + dbentry->data = NULL; + } + + return (SECSuccess); + +loser: + return (SECFailure); +} + +/** + ** Implement low level database access + **/ +static SECStatus +WriteDBEntry(NSSLOWCERTCertDBHandle *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, PLArenaPool *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] = (PRUint8)(entry->trust.sslFlags >> 8); + buf[1] = (PRUint8)(entry->trust.sslFlags); + buf[2] = (PRUint8)(entry->trust.emailFlags >> 8); + buf[3] = (PRUint8)(entry->trust.emailFlags); + buf[4] = (PRUint8)(entry->trust.objectSigningFlags >> 8); + buf[5] = (PRUint8)(entry->trust.objectSigningFlags); + buf[6] = (PRUint8)(entry->derCert.len >> 8); + buf[7] = (PRUint8)(entry->derCert.len); + buf[8] = (PRUint8)(nnlen >> 8); + buf[9] = (PRUint8)(nnlen); + + 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(const SECItem *certKey, PLArenaPool *arena, SECItem *dbkey) +{ + unsigned int len = certKey->len + SEC_DB_KEY_HEADER_LEN; + if (len > NSS_MAX_LEGACY_DB_KEY_SIZE) + goto loser; + if (arena) { + dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, len); + } else { + if (dbkey->len < len) { + dbkey->data = (unsigned char *)PORT_Alloc(len); + } + } + dbkey->len = 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(const SECItem *certKey, PLArenaPool *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; + if (dbkey->len > NSS_MAX_LEGACY_DB_KEY_SIZE) + goto loser; + 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; + unsigned 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: + case 8: + 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]); + lenoff = dbentry->len - (entry->derCert.len + nnlen + headerlen); + if (lenoff) { + if (lenoff < 0 || (lenoff & 0xffff) != 0) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + /* The cert size exceeded 64KB. Reconstruct the correct length. */ + entry->derCert.len += lenoff; + } + + /* Is data long enough? */ + if (dbentry->len < headerlen + entry->derCert.len) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + /* copy the dercert */ + entry->derCert.data = pkcs11_copyStaticData(&dbentry->data[headerlen], + entry->derCert.len, entry->derCertSpace, sizeof(entry->derCertSpace)); + if (entry->derCert.data == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* copy the nickname */ + if (nnlen > 1) { + /* Is data long enough? */ + if (dbentry->len < headerlen + entry->derCert.len + nnlen) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + entry->nickname = (char *)pkcs11_copyStaticData( + &dbentry->data[headerlen + entry->derCert.len], nnlen, + (unsigned char *)entry->nicknameSpace, + sizeof(entry->nicknameSpace)); + if (entry->nickname == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + } 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, + NSSLOWCERTCertTrust *trust, int flags) +{ + certDBEntryCert *entry; + PLArenaPool *arena = NULL; + int nnlen; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + + if (!arena) { + goto loser; + } + + entry = PORT_ArenaZNew(arena, 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; + PLArenaPool *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->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]; + + entry->derCert.data = (unsigned char *)PORT_ArenaAlloc(arena, certlen); + if (!entry->derCert.data) { + goto loser; + } + entry->derCert.len = certlen; + PORT_Memcpy(entry->derCert.data, &buf[DBCERT_V4_HEADER_LEN], certlen); + + if (nnlen) { + entry->nickname = (char *)PORT_ArenaAlloc(arena, nnlen); + if (!entry->nickname) { + goto loser; + } + PORT_Memcpy(entry->nickname, &buf[DBCERT_V4_HEADER_LEN + certlen], nnlen); + + if (PORT_Strcmp(entry->nickname, "Server-Cert") == 0) { + entry->trust.sslFlags |= CERTDB_USER; + } + } else { + entry->nickname = 0; + } + + 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(NSSLOWCERTCertDBHandle *handle, certDBEntryCert *entry) +{ + SECItem dbitem, dbkey; + PLArenaPool *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 = nsslowcert_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(NSSLOWCERTCertDBHandle *handle, SECItem *certKey) +{ + SECItem dbkey; + SECStatus rv; + + dbkey.data = NULL; + dbkey.len = 0; + + rv = EncodeDBCertKey(certKey, NULL, &dbkey); + if (rv != SECSuccess) { + goto loser; + } + + rv = DeleteDBEntry(handle, certDBEntryTypeCert, &dbkey); + if (rv == SECFailure) { + goto loser; + } + + PORT_Free(dbkey.data); + + return (SECSuccess); + +loser: + if (dbkey.data) { + PORT_Free(dbkey.data); + } + return (SECFailure); +} + +static certDBEntryCert * +CreateCertEntry(void) +{ + certDBEntryCert *entry; + + nsslowcert_LockFreeList(); + entry = entryListHead; + if (entry) { + entryListCount--; + entryListHead = entry->next; + } + PORT_Assert(entryListCount >= 0); + nsslowcert_UnlockFreeList(); + if (entry) { + return entry; + } + + return PORT_ZNew(certDBEntryCert); +} + +static void +DestroyCertEntryFreeList(void) +{ + certDBEntryCert *entry; + + nsslowcert_LockFreeList(); + while (NULL != (entry = entryListHead)) { + entryListCount--; + entryListHead = entry->next; + PORT_Free(entry); + } + PORT_Assert(!entryListCount); + entryListCount = 0; + nsslowcert_UnlockFreeList(); +} + +/* + * Read a certificate entry + */ +static certDBEntryCert * +ReadDBCertEntry(NSSLOWCERTCertDBHandle *handle, const SECItem *certKey) +{ + certDBEntryCert *entry; + SECItem dbkey; + SECItem dbentry; + SECStatus rv; + unsigned char buf[512]; + + dbkey.data = buf; + dbkey.len = sizeof(buf); + + entry = CreateCertEntry(); + if (entry == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + entry->common.arena = NULL; + entry->common.type = certDBEntryTypeCert; + + rv = EncodeDBCertKey(certKey, NULL, &dbkey); + if (rv != SECSuccess) { + goto loser; + } + + rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, NULL); + if (rv == SECFailure) { + goto loser; + } + + rv = DecodeDBCertEntry(entry, &dbentry); + if (rv != SECSuccess) { + goto loser; + } + + pkcs11_freeStaticData(dbkey.data, buf); + dbkey.data = NULL; + return (entry); + +loser: + pkcs11_freeStaticData(dbkey.data, buf); + dbkey.data = NULL; + if (entry) { + DestroyDBEntry((certDBEntry *)entry); + } + + return (NULL); +} + +/* + * encode a database cert record + */ +static SECStatus +EncodeDBCrlEntry(certDBEntryRevocation *entry, PLArenaPool *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] = (PRUint8)(entry->derCrl.len >> 8); + buf[1] = (PRUint8)(entry->derCrl.len); + buf[2] = (PRUint8)(nnlen >> 8); + buf[3] = (PRUint8)(nnlen); + + 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 urlLen; + int lenDiff; + + /* 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]); + urlLen = ((dbentry->data[2] << 8) | dbentry->data[3]); + lenDiff = dbentry->len - + (entry->derCrl.len + urlLen + DB_CRL_ENTRY_HEADER_LEN); + if (lenDiff) { + if (lenDiff < 0 || (lenDiff & 0xffff) != 0) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + /* CRL entry is greater than 64 K. Hack to make this continue to work */ + entry->derCrl.len += lenDiff; + } + + /* copy the der CRL */ + 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 (urlLen != 0) { + entry->url = (char *)PORT_ArenaAlloc(entry->common.arena, urlLen); + 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], + urlLen); + } + + 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; + PLArenaPool *arena = NULL; + int nnlen; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + + if (!arena) { + goto loser; + } + + entry = PORT_ArenaZNew(arena, 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(NSSLOWCERTCertDBHandle *handle, certDBEntryRevocation *entry, + SECItem *crlKey) +{ + SECItem dbkey; + PLArenaPool *tmparena = NULL; + SECItem encodedEntry; + SECStatus rv; + + tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (tmparena == NULL) { + goto loser; + } + + rv = EncodeDBCrlEntry(entry, tmparena, &encodedEntry); + if (rv == SECFailure) { + goto loser; + } + + rv = EncodeDBGenericKey(crlKey, 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(NSSLOWCERTCertDBHandle *handle, const SECItem *crlKey, + certDBEntryType crlType) +{ + SECItem dbkey; + PLArenaPool *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(NSSLOWCERTCertDBHandle *handle, SECItem *certKey, + certDBEntryType crlType) +{ + PLArenaPool *arena = NULL; + PLArenaPool *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, NULL); + 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); +} + +void +nsslowcert_DestroyDBEntry(certDBEntry *entry) +{ + DestroyDBEntry(entry); + return; +} + +/* + * Encode a database nickname record + */ +static SECStatus +EncodeDBNicknameEntry(certDBEntryNickname *entry, PLArenaPool *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) { + goto loser; + } + + /* fill in database record */ + buf = &dbitem->data[SEC_DB_ENTRY_HEADER_LEN]; + buf[0] = (PRUint8)(entry->subjectName.len >> 8); + buf[1] = (PRUint8)(entry->subjectName.len); + 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, PLArenaPool *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; + if (dbkey->len > NSS_MAX_LEGACY_DB_KEY_SIZE) + goto loser; + 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, + char *nickname) +{ + int lenDiff; + + /* 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]); + lenDiff = dbentry->len - + (entry->subjectName.len + DB_NICKNAME_ENTRY_HEADER_LEN); + if (lenDiff) { + if (lenDiff < 0 || (lenDiff & 0xffff) != 0) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + /* The entry size exceeded 64KB. Reconstruct the correct length. */ + entry->subjectName.len += lenDiff; + } + + /* 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); + entry->subjectName.type = siBuffer; + + entry->nickname = (char *)PORT_ArenaAlloc(entry->common.arena, + PORT_Strlen(nickname) + 1); + if (entry->nickname) { + PORT_Strcpy(entry->nickname, nickname); + } + + return (SECSuccess); + +loser: + return (SECFailure); +} + +/* + * create a new nickname entry + */ +static certDBEntryNickname * +NewDBNicknameEntry(char *nickname, SECItem *subjectName, unsigned int flags) +{ + PLArenaPool *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(NSSLOWCERTCertDBHandle *handle, char *nickname) +{ + PLArenaPool *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(NSSLOWCERTCertDBHandle *handle, char *nickname) +{ + PLArenaPool *arena = NULL; + PLArenaPool *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, nickname); + 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(NSSLOWCERTCertDBHandle *handle, certDBEntryNickname *entry) +{ + SECItem dbitem, dbkey; + PLArenaPool *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); +} + +static SECStatus +EncodeDBSMimeEntry(certDBEntrySMime *entry, PLArenaPool *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] = (PRUint8)(entry->subjectName.len >> 8); + buf[1] = (PRUint8)(entry->subjectName.len); + buf[2] = (PRUint8)(entry->smimeOptions.len >> 8); + buf[3] = (PRUint8)(entry->smimeOptions.len); + buf[4] = (PRUint8)(entry->optionsDate.len >> 8); + buf[5] = (PRUint8)(entry->optionsDate.len); + + /* 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, PLArenaPool *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; + if (dbkey->len > NSS_MAX_LEGACY_DB_KEY_SIZE) + goto loser; + 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) +{ + int lenDiff; + + /* 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]); + lenDiff = dbentry->len - (entry->subjectName.len + + entry->smimeOptions.len + + entry->optionsDate.len + + DB_SMIME_ENTRY_HEADER_LEN); + if (lenDiff) { + if (lenDiff < 0 || (lenDiff & 0xffff) != 0) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + /* The entry size exceeded 64KB. Reconstruct the correct length. */ + entry->subjectName.len += lenDiff; + } + + /* 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); + } else { + entry->smimeOptions.data = NULL; + } + 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); + } else { + entry->optionsDate.data = NULL; + } + + /* 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_ArenaAlloc(entry->common.arena, + 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) +{ + PLArenaPool *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(NSSLOWCERTCertDBHandle *handle, char *emailAddr) +{ + PLArenaPool *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 + */ +certDBEntrySMime * +nsslowcert_ReadDBSMimeEntry(NSSLOWCERTCertDBHandle *handle, char *emailAddr) +{ + PLArenaPool *arena = NULL; + PLArenaPool *tmparena = NULL; + certDBEntrySMime *entry = NULL; + 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_ArenaZAlloc(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(NSSLOWCERTCertDBHandle *handle, certDBEntrySMime *entry) +{ + SECItem dbitem, dbkey; + PLArenaPool *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, PLArenaPool *arena, + SECItem *dbitem) +{ + unsigned char *buf; + int len; + unsigned int ncerts; + unsigned int i; + unsigned char *tmpbuf; + unsigned int nnlen = 0; + unsigned int eaddrslen = 0; + int keyidoff; + SECItem *certKeys = entry->certKeys; + SECItem *keyIDs = entry->keyIDs; + ; + + if (entry->nickname) { + nnlen = PORT_Strlen(entry->nickname) + 1; + } + if (entry->emailAddrs) { + eaddrslen = 2; + for (i = 0; i < entry->nemailAddrs; i++) { + eaddrslen += PORT_Strlen(entry->emailAddrs[i]) + 1 + 2; + } + } + + ncerts = entry->ncerts; + + /* compute the length of the entry */ + keyidoff = DB_SUBJECT_ENTRY_HEADER_LEN + nnlen; + len = keyidoff + (4 * ncerts) + eaddrslen; + for (i = 0; i < ncerts; i++) { + if (keyIDs[i].len > 0xffff || + (certKeys[i].len > 0xffff)) { + PORT_SetError(SEC_ERROR_INPUT_LEN); + goto loser; + } + len += certKeys[i].len; + len += 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] = (PRUint8)(ncerts >> 8); + buf[1] = (PRUint8)(ncerts); + buf[2] = (PRUint8)(nnlen >> 8); + buf[3] = (PRUint8)(nnlen); + /* v7 email field is NULL in v8 */ + buf[4] = 0; + buf[5] = 0; + + PORT_Assert(DB_SUBJECT_ENTRY_HEADER_LEN == 6); + + if (entry->nickname) { + PORT_Memcpy(&buf[DB_SUBJECT_ENTRY_HEADER_LEN], entry->nickname, nnlen); + } + tmpbuf = &buf[keyidoff]; + for (i = 0; i < ncerts; i++) { + tmpbuf[0] = (PRUint8)(certKeys[i].len >> 8); + tmpbuf[1] = (PRUint8)(certKeys[i].len); + tmpbuf += 2; + } + for (i = 0; i < ncerts; i++) { + tmpbuf[0] = (PRUint8)(keyIDs[i].len >> 8); + tmpbuf[1] = (PRUint8)(keyIDs[i].len); + tmpbuf += 2; + } + + for (i = 0; i < ncerts; i++) { + PORT_Memcpy(tmpbuf, certKeys[i].data, certKeys[i].len); + tmpbuf += certKeys[i].len; + } + for (i = 0; i < ncerts; i++) { + if (keyIDs[i].len) { + PORT_Memcpy(tmpbuf, keyIDs[i].data, keyIDs[i].len); + tmpbuf += keyIDs[i].len; + } + } + + if (entry->emailAddrs) { + tmpbuf[0] = (PRUint8)(entry->nemailAddrs >> 8); + tmpbuf[1] = (PRUint8)(entry->nemailAddrs); + tmpbuf += 2; + for (i = 0; i < entry->nemailAddrs; i++) { + int nameLen = PORT_Strlen(entry->emailAddrs[i]) + 1; + tmpbuf[0] = (PRUint8)(nameLen >> 8); + tmpbuf[1] = (PRUint8)(nameLen); + tmpbuf += 2; + PORT_Memcpy(tmpbuf, entry->emailAddrs[i], nameLen); + tmpbuf += nameLen; + } + } + + PORT_Assert(tmpbuf == &buf[len]); + + return (SECSuccess); + +loser: + return (SECFailure); +} + +/* + * Encode a database key for a subject record + */ +static SECStatus +EncodeDBSubjectKey(SECItem *derSubject, PLArenaPool *arena, + SECItem *dbkey) +{ + dbkey->len = derSubject->len + SEC_DB_KEY_HEADER_LEN; + if (dbkey->len > NSS_MAX_LEGACY_DB_KEY_SIZE) + goto loser; + 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, + const SECItem *derSubject) +{ + PLArenaPool *arena = entry->common.arena; + unsigned char *tmpbuf; + unsigned char *end; + void *mark = PORT_ArenaMark(arena); + unsigned int eaddrlen; + unsigned int i; + unsigned int keyidoff; + unsigned int len; + unsigned int ncerts = 0; + unsigned int nnlen; + SECStatus rv; + + 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]); + keyidoff = DB_SUBJECT_ENTRY_HEADER_LEN + nnlen + eaddrlen; + len = keyidoff + (4 * ncerts); + if (dbentry->len < len) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + entry->certKeys = PORT_ArenaNewArray(arena, SECItem, ncerts); + entry->keyIDs = PORT_ArenaNewArray(arena, 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 we have an old style email entry, there is only one */ + entry->nemailAddrs = 0; + if (eaddrlen > 1) { /* null terminator is stored */ + entry->emailAddrs = PORT_ArenaNewArray(arena, char *, 2); + if (entry->emailAddrs == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + entry->emailAddrs[0] = (char *)PORT_ArenaAlloc(arena, eaddrlen); + if (entry->emailAddrs[0] == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + PORT_Memcpy(entry->emailAddrs[0], + &dbentry->data[DB_SUBJECT_ENTRY_HEADER_LEN + nnlen], + eaddrlen); + entry->nemailAddrs = 1; + } else { + entry->emailAddrs = NULL; + } + + /* collect the lengths of the certKeys and keyIDs, and total the + * overall length. + */ + tmpbuf = &dbentry->data[keyidoff]; + for (i = 0; i < ncerts; i++) { + unsigned int itemlen = (tmpbuf[0] << 8) | tmpbuf[1]; + entry->certKeys[i].len = itemlen; + len += itemlen; + tmpbuf += 2; + } + for (i = 0; i < ncerts; i++) { + unsigned int itemlen = (tmpbuf[0] << 8) | tmpbuf[1]; + entry->keyIDs[i].len = itemlen; + len += itemlen; + tmpbuf += 2; + } + + /* is encoded entry large enough ? */ + if (len > dbentry->len) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + goto loser; + } + + for (i = 0; i < ncerts; i++) { + unsigned int kLen = entry->certKeys[i].len; + entry->certKeys[i].data = (unsigned char *)PORT_ArenaAlloc(arena, kLen); + if (entry->certKeys[i].data == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + PORT_Memcpy(entry->certKeys[i].data, tmpbuf, kLen); + tmpbuf += kLen; + } + for (i = 0; i < ncerts; i++) { + unsigned int iLen = entry->keyIDs[i].len; + entry->keyIDs[i].data = (unsigned char *)PORT_ArenaAlloc(arena, iLen); + if (entry->keyIDs[i].data == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + PORT_Memcpy(entry->keyIDs[i].data, tmpbuf, iLen); + tmpbuf += iLen; + } + + end = dbentry->data + dbentry->len; + if ((eaddrlen == 0) && (end - tmpbuf > 1)) { + /* read in the additional email addresses */ + entry->nemailAddrs = (((unsigned int)tmpbuf[0]) << 8) | tmpbuf[1]; + tmpbuf += 2; + if (end - tmpbuf < 2 * (int)entry->nemailAddrs) + goto loser; + entry->emailAddrs = PORT_ArenaNewArray(arena, char *, entry->nemailAddrs); + if (entry->emailAddrs == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + for (i = 0; i < entry->nemailAddrs; i++) { + int nameLen; + if (end - tmpbuf < 2) { + goto loser; + } + nameLen = (((int)tmpbuf[0]) << 8) | tmpbuf[1]; + tmpbuf += 2; + if (end - tmpbuf < nameLen) { + goto loser; + } + entry->emailAddrs[i] = PORT_ArenaAlloc(arena, nameLen); + if (entry->emailAddrs == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + PORT_Memcpy(entry->emailAddrs[i], tmpbuf, nameLen); + tmpbuf += nameLen; + } + if (tmpbuf != end) + goto loser; + } + PORT_ArenaUnmark(arena, mark); + return (SECSuccess); + +loser: + PORT_ArenaRelease(arena, mark); /* discard above allocations */ + 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) +{ + PLArenaPool *arena = NULL; + certDBEntrySubject *entry; + SECStatus rv; + unsigned int nnlen; + + 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; + entry->nemailAddrs = 0; + /* 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 = nsslowcert_FixupEmailAddr(emailAddr); + if (emailAddr == NULL) { + entry->emailAddrs = NULL; + goto loser; + } + + entry->emailAddrs = (char **)PORT_ArenaAlloc(arena, sizeof(char *)); + if (entry->emailAddrs == NULL) { + PORT_Free(emailAddr); + goto loser; + } + entry->emailAddrs[0] = PORT_ArenaStrdup(arena, emailAddr); + if (entry->emailAddrs[0]) { + entry->nemailAddrs = 1; + } + + PORT_Free(emailAddr); + } else { + entry->emailAddrs = 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(NSSLOWCERTCertDBHandle *handle, SECItem *derSubject) +{ + SECItem dbkey; + PLArenaPool *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(NSSLOWCERTCertDBHandle *handle, SECItem *derSubject) +{ + /* |arena| isn't function-bounded, so cannot be a PORTCheapArenaPool. */ + PLArenaPool *arena = NULL; + PORTCheapArenaPool tmpArena; + + certDBEntrySubject *entry; + SECItem dbkey; + SECItem dbentry; + SECStatus rv; + + PORT_InitCheapArena(&tmpArena, DER_DEFAULT_CHUNKSIZE); + 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; + } + entry->common.arena = arena; + entry->common.type = certDBEntryTypeSubject; + + rv = EncodeDBSubjectKey(derSubject, &tmpArena.arena, &dbkey); + if (rv != SECSuccess) { + goto loser; + } + + rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, &tmpArena.arena); + if (rv == SECFailure) { + goto loser; + } + + rv = DecodeDBSubjectEntry(entry, &dbentry, derSubject); + if (rv == SECFailure) { + goto loser; + } + + PORT_DestroyCheapArena(&tmpArena); + return (entry); + +loser: + PORT_DestroyCheapArena(&tmpArena); + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + + return (NULL); +} + +/* + * Encode a subject name entry into byte stream suitable for + * the database + */ +static SECStatus +WriteDBSubjectEntry(NSSLOWCERTCertDBHandle *handle, certDBEntrySubject *entry) +{ + SECItem dbitem, dbkey; + PLArenaPool *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); +} + +typedef enum { nsslowcert_remove, + nsslowcert_add } nsslowcertUpdateType; + +static SECStatus +nsslowcert_UpdateSubjectEmailAddr(NSSLOWCERTCertDBHandle *dbhandle, + SECItem *derSubject, char *emailAddr, nsslowcertUpdateType updateType) +{ + certDBEntrySubject *entry = NULL; + int index = -1, i; + SECStatus rv; + + if (emailAddr) { + emailAddr = nsslowcert_FixupEmailAddr(emailAddr); + if (emailAddr == NULL) { + return SECFailure; + } + } else { + return SECSuccess; + } + + entry = ReadDBSubjectEntry(dbhandle, derSubject); + if (entry == NULL) { + rv = SECFailure; + goto done; + } + + for (i = 0; i < (int)(entry->nemailAddrs); i++) { + if (PORT_Strcmp(entry->emailAddrs[i], emailAddr) == 0) { + index = i; + } + } + + if (updateType == nsslowcert_remove) { + if (index == -1) { + rv = SECSuccess; + goto done; + } + entry->nemailAddrs--; + for (i = index; i < (int)(entry->nemailAddrs); i++) { + entry->emailAddrs[i] = entry->emailAddrs[i + 1]; + } + } else { + char **newAddrs = NULL; + + if (index != -1) { + rv = SECSuccess; + goto done; + } + newAddrs = (char **)PORT_ArenaAlloc(entry->common.arena, + (entry->nemailAddrs + 1) * sizeof(char *)); + if (!newAddrs) { + rv = SECFailure; + goto done; + } + for (i = 0; i < (int)(entry->nemailAddrs); i++) { + newAddrs[i] = entry->emailAddrs[i]; + } + newAddrs[entry->nemailAddrs] = + PORT_ArenaStrdup(entry->common.arena, emailAddr); + if (!newAddrs[entry->nemailAddrs]) { + rv = SECFailure; + goto done; + } + entry->emailAddrs = newAddrs; + entry->nemailAddrs++; + } + + /* delete the subject entry */ + DeleteDBSubjectEntry(dbhandle, derSubject); + + /* write the new one */ + rv = WriteDBSubjectEntry(dbhandle, entry); + +done: + if (entry) + DestroyDBEntry((certDBEntry *)entry); + if (emailAddr) + PORT_Free(emailAddr); + return rv; +} + +/* + * writes a nickname to an existing subject entry that does not currently + * have one + */ +static SECStatus +AddNicknameToSubject(NSSLOWCERTCertDBHandle *dbhandle, + NSSLOWCERTCertificate *cert, char *nickname) +{ + certDBEntrySubject *entry; + SECStatus rv; + + if (nickname == NULL) { + return (SECFailure); + } + + entry = ReadDBSubjectEntry(dbhandle, &cert->derSubject); + PORT_Assert(entry != NULL); + if (entry == NULL) { + goto loser; + } + + PORT_Assert(entry->nickname == NULL); + if (entry->nickname != NULL) { + goto loser; + } + + entry->nickname = PORT_ArenaStrdup(entry->common.arena, nickname); + + if (entry->nickname == NULL) { + goto loser; + } + + /* delete the subject entry */ + DeleteDBSubjectEntry(dbhandle, &cert->derSubject); + + /* write the new one */ + rv = WriteDBSubjectEntry(dbhandle, entry); + if (rv != SECSuccess) { + goto loser; + } + + DestroyDBEntry((certDBEntry *)entry); + return (SECSuccess); + +loser: + DestroyDBEntry((certDBEntry *)entry); + return (SECFailure); +} + +/* + * create a new version entry + */ +static certDBEntryVersion * +NewDBVersionEntry(unsigned int flags) +{ + PLArenaPool *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(NSSLOWCERTCertDBHandle *handle) +{ + PLArenaPool *arena = NULL; + PLArenaPool *tmparena = NULL; + certDBEntryVersion *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 = PORT_ArenaZNew(arena, 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); + + rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena); + 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 version entry into byte stream suitable for + * the database + */ +static SECStatus +WriteDBVersionEntry(NSSLOWCERTCertDBHandle *handle, certDBEntryVersion *entry) +{ + SECItem dbitem, dbkey; + PLArenaPool *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); +} + +/* + * cert is no longer a perm cert, but will remain a temp cert + */ +static SECStatus +RemovePermSubjectNode(NSSLOWCERTCertificate *cert) +{ + certDBEntrySubject *entry; + unsigned int i; + SECStatus rv; + + entry = ReadDBSubjectEntry(cert->dbhandle, &cert->derSubject); + 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 (entry->emailAddrs) { + /* if the subject had an email record, then delete it too */ + for (i = 0; i < entry->nemailAddrs; i++) { + DeleteDBSMimeEntry(cert->dbhandle, entry->emailAddrs[i]); + } + } + if (entry->nickname) { + DeleteDBNicknameEntry(cert->dbhandle, entry->nickname); + } + + DeleteDBSubjectEntry(cert->dbhandle, &cert->derSubject); + } + DestroyDBEntry((certDBEntry *)entry); + + return (rv); +} + +/* + * add a cert to the perm subject list + */ +static SECStatus +AddPermSubjectNode(certDBEntrySubject *entry, NSSLOWCERTCertificate *cert, + char *nickname) +{ + SECItem *newCertKeys, *newKeyIDs; + unsigned int i, new_i; + SECStatus rv; + unsigned int ncerts; + + PORT_Assert(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 */ + entry->nickname = PORT_ArenaStrdup(entry->common.arena, nickname); + if (entry->nickname == NULL) { + return (SECFailure); + } + } + + /* a DB entry already exists, so add this cert */ + newCertKeys = PORT_ArenaZNewArray(entry->common.arena, SECItem, ncerts + 1); + newKeyIDs = PORT_ArenaZNewArray(entry->common.arena, SECItem, ncerts + 1); + + if ((newCertKeys == NULL) || (newKeyIDs == NULL)) { + return (SECFailure); + } + + /* Step 1: copy certs older than "cert" into new entry. */ + for (i = 0, new_i = 0; i < ncerts; i++) { + NSSLOWCERTCertificate *cmpcert; + PRBool isNewer; + cmpcert = nsslowcert_FindCertByKey(cert->dbhandle, + &entry->certKeys[i]); + /* The entry has been corrupted, remove it from the list */ + if (!cmpcert) { + continue; + } + + isNewer = nsslowcert_IsNewer(cert, cmpcert); + nsslowcert_DestroyCertificate(cmpcert); + if (isNewer) + break; + /* copy this cert entry */ + newCertKeys[new_i] = entry->certKeys[i]; + newKeyIDs[new_i] = entry->keyIDs[i]; + new_i++; + } + + /* Step 2: Add "cert" to the entry. */ + rv = SECITEM_CopyItem(entry->common.arena, &newCertKeys[new_i], + &cert->certKey); + if (rv != SECSuccess) { + return (SECFailure); + } + rv = SECITEM_CopyItem(entry->common.arena, &newKeyIDs[new_i], + &cert->subjectKeyID); + if (rv != SECSuccess) { + return (SECFailure); + } + new_i++; + + /* Step 3: copy remaining certs (if any) from old entry to new. */ + for (; i < ncerts; i++, new_i++) { + newCertKeys[new_i] = entry->certKeys[i]; + newKeyIDs[new_i] = entry->keyIDs[i]; + } + + /* update certKeys and keyIDs */ + entry->certKeys = newCertKeys; + entry->keyIDs = newKeyIDs; + + /* set new count value */ + entry->ncerts = new_i; + + DeleteDBSubjectEntry(cert->dbhandle, &cert->derSubject); + rv = WriteDBSubjectEntry(cert->dbhandle, entry); + return (rv); +} + +SECStatus +nsslowcert_TraversePermCertsForSubject(NSSLOWCERTCertDBHandle *handle, + SECItem *derSubject, + NSSLOWCERTCertCallback cb, void *cbarg) +{ + certDBEntrySubject *entry; + unsigned int i; + NSSLOWCERTCertificate *cert; + SECStatus rv = SECSuccess; + + entry = ReadDBSubjectEntry(handle, derSubject); + + if (entry == NULL) { + return (SECFailure); + } + + for (i = 0; i < entry->ncerts; i++) { + cert = nsslowcert_FindCertByKey(handle, &entry->certKeys[i]); + if (!cert) { + continue; + } + rv = (*cb)(cert, cbarg); + nsslowcert_DestroyCertificate(cert); + if (rv == SECFailure) { + break; + } + } + + DestroyDBEntry((certDBEntry *)entry); + + return (rv); +} + +int +nsslowcert_NumPermCertsForSubject(NSSLOWCERTCertDBHandle *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 +nsslowcert_TraversePermCertsForNickname(NSSLOWCERTCertDBHandle *handle, + char *nickname, NSSLOWCERTCertCallback 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 = nsslowcert_ReadDBSMimeEntry(handle, nickname); + if (smentry) { + derSubject = &smentry->subjectName; + } + } + + if (derSubject) { + rv = nsslowcert_TraversePermCertsForSubject(handle, derSubject, + cb, cbarg); + } else { + rv = SECFailure; + } + + if (nnentry) { + DestroyDBEntry((certDBEntry *)nnentry); + } + if (smentry) { + DestroyDBEntry((certDBEntry *)smentry); + } + + return (rv); +} + +int +nsslowcert_NumPermCertsForNickname(NSSLOWCERTCertDBHandle *handle, + char *nickname) +{ + certDBEntryNickname *entry; + int ret; + + entry = ReadDBNicknameEntry(handle, nickname); + + if (entry) { + ret = nsslowcert_NumPermCertsForSubject(handle, &entry->subjectName); + DestroyDBEntry((certDBEntry *)entry); + } else { + ret = 0; + } + return (ret); +} + +/* + * add a nickname to a cert that doesn't have one + */ +static SECStatus +AddNicknameToPermCert(NSSLOWCERTCertDBHandle *dbhandle, + NSSLOWCERTCertificate *cert, char *nickname) +{ + certDBEntryCert *entry; + int rv; + + entry = cert->dbEntry; + PORT_Assert(entry != NULL); + if (entry == NULL) { + goto loser; + } + + pkcs11_freeNickname(entry->nickname, entry->nicknameSpace); + entry->nickname = NULL; + entry->nickname = pkcs11_copyNickname(nickname, entry->nicknameSpace, + sizeof(entry->nicknameSpace)); + + rv = WriteDBCertEntry(dbhandle, entry); + if (rv) { + goto loser; + } + + pkcs11_freeNickname(cert->nickname, cert->nicknameSpace); + cert->nickname = NULL; + cert->nickname = pkcs11_copyNickname(nickname, cert->nicknameSpace, + sizeof(cert->nicknameSpace)); + + 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 +nsslowcert_AddPermNickname(NSSLOWCERTCertDBHandle *dbhandle, + NSSLOWCERTCertificate *cert, char *nickname) +{ + SECStatus rv = SECFailure; + certDBEntrySubject *entry = NULL; + certDBEntryNickname *nicknameEntry = NULL; + + nsslowcert_LockDB(dbhandle); + + entry = ReadDBSubjectEntry(dbhandle, &cert->derSubject); + if (entry == NULL) + goto loser; + + if (entry->nickname == NULL) { + + /* no nickname for subject */ + rv = AddNicknameToSubject(dbhandle, cert, nickname); + if (rv != SECSuccess) { + goto loser; + } + rv = AddNicknameToPermCert(dbhandle, cert, nickname); + if (rv != SECSuccess) { + goto loser; + } + nicknameEntry = NewDBNicknameEntry(nickname, &cert->derSubject, 0); + if (nicknameEntry == NULL) { + goto loser; + } + + rv = WriteDBNicknameEntry(dbhandle, nicknameEntry); + if (rv != SECSuccess) { + goto loser; + } + } else { + /* subject already has a nickname */ + rv = AddNicknameToPermCert(dbhandle, cert, entry->nickname); + if (rv != SECSuccess) { + goto loser; + } + /* make sure nickname entry exists. If the database was corrupted, + * we may have lost the nickname entry. Add it back now */ + nicknameEntry = ReadDBNicknameEntry(dbhandle, entry->nickname); + if (nicknameEntry == NULL) { + nicknameEntry = NewDBNicknameEntry(entry->nickname, + &cert->derSubject, 0); + if (nicknameEntry == NULL) { + goto loser; + } + + rv = WriteDBNicknameEntry(dbhandle, nicknameEntry); + if (rv != SECSuccess) { + goto loser; + } + } + } + rv = SECSuccess; + +loser: + if (entry) { + DestroyDBEntry((certDBEntry *)entry); + } + if (nicknameEntry) { + DestroyDBEntry((certDBEntry *)nicknameEntry); + } + nsslowcert_UnlockDB(dbhandle); + return (rv); +} + +static certDBEntryCert * +AddCertToPermDB(NSSLOWCERTCertDBHandle *handle, NSSLOWCERTCertificate *cert, + char *nickname, NSSLOWCERTCertTrust *trust) +{ + certDBEntryCert *certEntry = NULL; + certDBEntryNickname *nicknameEntry = NULL; + certDBEntrySubject *subjectEntry = NULL; + int state = 0; + SECStatus rv; + PRBool donnentry = PR_FALSE; + + if (nickname) { + donnentry = PR_TRUE; + } + + subjectEntry = ReadDBSubjectEntry(handle, &cert->derSubject); + + if (subjectEntry && subjectEntry->nickname) { + donnentry = PR_FALSE; + nickname = subjectEntry->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; + + /* "Change" handles if necessary */ + cert->dbhandle = handle; + + /* add to or create new subject entry */ + if (subjectEntry) { + /* REWRITE BASED ON SUBJECT ENTRY */ + rv = AddPermSubjectNode(subjectEntry, 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. + */ + /* where does subjectKeyID and certKey come from? */ + 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) { + DeleteDBCertEntry(handle, &cert->certKey); + } + if ((state > 1) && donnentry) { + DeleteDBNicknameEntry(handle, nickname); + } + if (certEntry) { + DestroyDBEntry((certDBEntry *)certEntry); + } + if (nicknameEntry) { + DestroyDBEntry((certDBEntry *)nicknameEntry); + } + if (subjectEntry) { + DestroyDBEntry((certDBEntry *)subjectEntry); + } + + return (NULL); +} + +/* forward declaration */ +static SECStatus +UpdateV7DB(NSSLOWCERTCertDBHandle *handle, DB *updatedb); + +/* + * version 8 uses the same schema as version 7. The only differences are + * 1) version 8 db uses the blob shim to store data entries > 32k. + * 2) version 8 db sets the db block size to 32k. + * both of these are dealt with by the handle. + */ + +static SECStatus +UpdateV8DB(NSSLOWCERTCertDBHandle *handle, DB *updatedb) +{ + return UpdateV7DB(handle, updatedb); +} + +/* + * we could just blindly sequence through reading key data pairs and writing + * them back out, but some cert.db's have gotten quite large and may have some + * subtle corruption problems, so instead we cycle through the certs and + * CRL's and S/MIME profiles and rebuild our subject lists from those records. + */ +static SECStatus +UpdateV7DB(NSSLOWCERTCertDBHandle *handle, DB *updatedb) +{ + DBT key, data; + int ret; + NSSLOWCERTCertificate *cert; + PRBool isKRL = PR_FALSE; + certDBEntryType entryType; + SECItem dbEntry, dbKey; + certDBEntryRevocation crlEntry; + certDBEntryCert certEntry; + certDBEntrySMime smimeEntry; + SECStatus rv; + + ret = (*updatedb->seq)(updatedb, &key, &data, R_FIRST); + + if (ret) { + return (SECFailure); + } + + do { + unsigned char *dataBuf = (unsigned char *)data.data; + unsigned char *keyBuf = (unsigned char *)key.data; + dbEntry.data = &dataBuf[SEC_DB_ENTRY_HEADER_LEN]; + dbEntry.len = data.size - SEC_DB_ENTRY_HEADER_LEN; + entryType = (certDBEntryType)keyBuf[0]; + dbKey.data = &keyBuf[SEC_DB_KEY_HEADER_LEN]; + dbKey.len = key.size - SEC_DB_KEY_HEADER_LEN; + if ((dbEntry.len <= 0) || (dbKey.len <= 0)) { + continue; + } + + switch (entryType) { + /* these entries will get regenerated as we read the + * rest of the data from the database */ + case certDBEntryTypeVersion: + case certDBEntryTypeSubject: + case certDBEntryTypeContentVersion: + case certDBEntryTypeNickname: + /* smime profiles need entries created after the certs have + * been imported, loop over them in a second run */ + case certDBEntryTypeSMimeProfile: + break; + + case certDBEntryTypeCert: + /* decode Entry */ + certEntry.common.version = (unsigned int)dataBuf[0]; + certEntry.common.type = entryType; + certEntry.common.flags = (unsigned int)dataBuf[2]; + rv = DecodeDBCertEntry(&certEntry, &dbEntry); + if (rv != SECSuccess) { + break; + } + /* should we check for existing duplicates? */ + cert = nsslowcert_DecodeDERCertificate(&certEntry.derCert, + certEntry.nickname); + if (cert) { + nsslowcert_UpdatePermCert(handle, cert, certEntry.nickname, + &certEntry.trust); + nsslowcert_DestroyCertificate(cert); + } + /* free any data the decode may have allocated. */ + pkcs11_freeStaticData(certEntry.derCert.data, + certEntry.derCertSpace); + pkcs11_freeNickname(certEntry.nickname, certEntry.nicknameSpace); + break; + + case certDBEntryTypeKeyRevocation: + isKRL = PR_TRUE; + /* fall through */ + case certDBEntryTypeRevocation: + crlEntry.common.version = (unsigned int)dataBuf[0]; + crlEntry.common.type = entryType; + crlEntry.common.flags = (unsigned int)dataBuf[2]; + crlEntry.common.arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (crlEntry.common.arena == NULL) { + break; + } + rv = DecodeDBCrlEntry(&crlEntry, &dbEntry); + if (rv != SECSuccess) { + break; + } + nsslowcert_UpdateCrl(handle, &crlEntry.derCrl, &dbKey, + crlEntry.url, isKRL); + /* free data allocated by the decode */ + PORT_FreeArena(crlEntry.common.arena, PR_FALSE); + crlEntry.common.arena = NULL; + break; + + default: + break; + } + } while ((*updatedb->seq)(updatedb, &key, &data, R_NEXT) == 0); + + /* now loop again updating just the SMimeProfile. */ + ret = (*updatedb->seq)(updatedb, &key, &data, R_FIRST); + + if (ret) { + return (SECFailure); + } + + do { + unsigned char *dataBuf = (unsigned char *)data.data; + unsigned char *keyBuf = (unsigned char *)key.data; + dbEntry.data = &dataBuf[SEC_DB_ENTRY_HEADER_LEN]; + dbEntry.len = data.size - SEC_DB_ENTRY_HEADER_LEN; + entryType = (certDBEntryType)keyBuf[0]; + if (entryType != certDBEntryTypeSMimeProfile) { + continue; + } + dbKey.data = &keyBuf[SEC_DB_KEY_HEADER_LEN]; + dbKey.len = key.size - SEC_DB_KEY_HEADER_LEN; + if ((dbEntry.len <= 0) || (dbKey.len <= 0)) { + continue; + } + smimeEntry.common.version = (unsigned int)dataBuf[0]; + smimeEntry.common.type = entryType; + smimeEntry.common.flags = (unsigned int)dataBuf[2]; + smimeEntry.common.arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + /* decode entry */ + rv = DecodeDBSMimeEntry(&smimeEntry, &dbEntry, (char *)dbKey.data); + if (rv == SECSuccess) { + nsslowcert_UpdateSMimeProfile(handle, smimeEntry.emailAddr, + &smimeEntry.subjectName, &smimeEntry.smimeOptions, + &smimeEntry.optionsDate); + } + PORT_FreeArena(smimeEntry.common.arena, PR_FALSE); + smimeEntry.common.arena = NULL; + } while ((*updatedb->seq)(updatedb, &key, &data, R_NEXT) == 0); + + (*updatedb->close)(updatedb); + + /* a database update is a good time to go back and verify the integrity of + * the keys and certs */ + handle->dbVerify = PR_TRUE; + return (SECSuccess); +} + +/* + * 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(NSSLOWCERTCertDBHandle *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; + + /* + * 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; + } + if (ret) { + return SECFailure; + } + } + } + } while ((*updatedb->seq)(updatedb, &key, &data, R_NEXT) == 0); + + ret = certdb_Sync(handle->permCertDB, 0); + if (ret) { + return SECFailure; + } + + 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); + (void)WriteDBSubjectEntry(handle, subjectEntry); + } + } else if (type == certDBEntryTypeSMimeProfile) { + emailAddr = &((char *)key.data)[1]; + + /* get the matching smime entry in the new DB */ + emailEntry = nsslowcert_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->emailAddrs = (char **) + PORT_ArenaAlloc(subjectEntry->common.arena, + sizeof(char *)); + if (subjectEntry->emailAddrs) { + subjectEntry->emailAddrs[0] = + (char *)PORT_ArenaAlloc(subjectEntry->common.arena, + key.size - 1); + if (subjectEntry->emailAddrs[0]) { + PORT_Memcpy(subjectEntry->emailAddrs[0], emailAddr, + key.size - 1); + subjectEntry->nemailAddrs = 1; + (void)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); + if (ret) { + return SECFailure; + } + + (*updatedb->close)(updatedb); + return (SECSuccess); +} + +static SECStatus +updateV5Callback(NSSLOWCERTCertificate *cert, SECItem *k, void *pdata) +{ + NSSLOWCERTCertDBHandle *handle; + certDBEntryCert *entry; + NSSLOWCERTCertTrust *trust; + + handle = (NSSLOWCERTCertDBHandle *)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; + } + /* servers didn't set the user flags on the server cert.. */ + if (PORT_Strcmp(cert->dbEntry->nickname, "Server-Cert") == 0) { + trust->sslFlags |= CERTDB_USER; + } + + entry = AddCertToPermDB(handle, cert, cert->dbEntry->nickname, + &cert->dbEntry->trust); + if (entry) { + DestroyDBEntry((certDBEntry *)entry); + } + + return (SECSuccess); +} + +static SECStatus +UpdateV5DB(NSSLOWCERTCertDBHandle *handle, DB *updatedb) +{ + NSSLOWCERTCertDBHandle updatehandle; + + updatehandle.permCertDB = updatedb; + updatehandle.dbMon = PZ_NewMonitor(nssILockCertDB); + updatehandle.dbVerify = 0; + updatehandle.ref = 1; /* prevent premature close */ + + (void)nsslowcert_TraversePermCerts(&updatehandle, updateV5Callback, + (void *)handle); + + PZ_DestroyMonitor(updatehandle.dbMon); + + (*updatedb->close)(updatedb); + return (SECSuccess); +} + +static PRBool +isV4DB(DB *db) +{ + DBT key, data; + int ret; + + key.data = "Version"; + key.size = 7; + + ret = (*db->get)(db, &key, &data, 0); + if (ret) { + return PR_FALSE; + } + + if ((data.size == 1) && (*(unsigned char *)data.data <= 4)) { + return PR_TRUE; + } + + return PR_FALSE; +} + +static SECStatus +UpdateV4DB(NSSLOWCERTCertDBHandle *handle, DB *updatedb) +{ + DBT key, data; + certDBEntryCert *entry, *entry2; + int ret; + NSSLOWCERTCertificate *cert; + + ret = (*updatedb->seq)(updatedb, &key, &data, R_FIRST); + + if (ret) { + return (SECFailure); + } + + do { + if (data.size != 1) { /* skip version number */ + + /* decode the old DB entry */ + entry = (certDBEntryCert *) + DecodeV4DBCertEntry((unsigned char *)data.data, data.size); + + if (entry) { + cert = nsslowcert_DecodeDERCertificate(&entry->derCert, + entry->nickname); + + if (cert != NULL) { + /* add to new database */ + entry2 = AddCertToPermDB(handle, cert, entry->nickname, + &entry->trust); + + nsslowcert_DestroyCertificate(cert); + if (entry2) { + DestroyDBEntry((certDBEntry *)entry2); + } + } + DestroyDBEntry((certDBEntry *)entry); + } + } + } while ((*updatedb->seq)(updatedb, &key, &data, R_NEXT) == 0); + + (*updatedb->close)(updatedb); + return (SECSuccess); +} + +/* + * return true if a database key conflict exists + */ +PRBool +nsslowcert_CertDBKeyConflict(SECItem *derCert, NSSLOWCERTCertDBHandle *handle) +{ + SECStatus rv; + DBT tmpdata; + DBT namekey; + int ret; + SECItem keyitem; + PLArenaPool *arena = NULL; + SECItem derKey; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + goto loser; + } + + /* get the db key of the cert */ + rv = nsslowcert_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; + + 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); +} + +/* + * return true if a nickname conflict exists + * NOTE: caller must have already made sure that this exact cert + * doesn't exist in the DB + */ +static PRBool +nsslowcert_CertNicknameConflict(char *nickname, SECItem *derSubject, + NSSLOWCERTCertDBHandle *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); +} + +#ifdef DBM_USING_NSPR +#define NO_RDONLY PR_RDONLY +#define NO_RDWR PR_RDWR +#define NO_CREATE (PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE) +#else +#define NO_RDONLY O_RDONLY +#define NO_RDWR O_RDWR +#define NO_CREATE (O_RDWR | O_CREAT | O_TRUNC) +#endif + +/* + * open an old database that needs to be updated + */ +static DB * +nsslowcert_openolddb(NSSLOWCERTDBNameFunc namecb, void *cbarg, int version) +{ + char *tmpname; + DB *updatedb = NULL; + + tmpname = (*namecb)(cbarg, version); /* get v6 db name */ + if (tmpname) { + updatedb = dbopen(tmpname, NO_RDONLY, 0600, DB_HASH, 0); + PORT_Free(tmpname); + } + return updatedb; +} + +static SECStatus +openNewCertDB(const char *appName, const char *prefix, const char *certdbname, + NSSLOWCERTCertDBHandle *handle, NSSLOWCERTDBNameFunc namecb, void *cbarg) +{ + SECStatus rv; + certDBEntryVersion *versionEntry = NULL; + DB *updatedb = NULL; + int status = RDB_FAIL; + + if (appName) { + handle->permCertDB = rdbopen(appName, prefix, "cert", NO_CREATE, &status); + } else { + handle->permCertDB = dbsopen(certdbname, NO_CREATE, 0600, DB_HASH, 0); + } + + /* if create fails then we lose */ + if (handle->permCertDB == 0) { + return status == RDB_RETRY ? SECWouldBlock : SECFailure; + } + + /* Verify version number; */ + versionEntry = NewDBVersionEntry(0); + if (versionEntry == NULL) { + rv = SECFailure; + goto loser; + } + + rv = WriteDBVersionEntry(handle, versionEntry); + + DestroyDBEntry((certDBEntry *)versionEntry); + + if (rv != SECSuccess) { + goto loser; + } + + /* rv must already be Success here because of previous if statement */ + /* try to upgrade old db here */ + if (appName && + (updatedb = dbsopen(certdbname, NO_RDONLY, 0600, DB_HASH, 0)) != NULL) { + rv = UpdateV8DB(handle, updatedb); + } else if ((updatedb = nsslowcert_openolddb(namecb, cbarg, 7)) != NULL) { + rv = UpdateV7DB(handle, updatedb); + } else if ((updatedb = nsslowcert_openolddb(namecb, cbarg, 6)) != NULL) { + rv = UpdateV6DB(handle, updatedb); + } else if ((updatedb = nsslowcert_openolddb(namecb, cbarg, 5)) != NULL) { + rv = UpdateV5DB(handle, updatedb); + } else if ((updatedb = nsslowcert_openolddb(namecb, cbarg, 4)) != NULL) { + /* NES has v5 format db's with v4 db names! */ + if (isV4DB(updatedb)) { + rv = UpdateV4DB(handle, updatedb); + } else { + rv = UpdateV5DB(handle, updatedb); + } + } + +loser: + db_InitComplete(handle->permCertDB); + return rv; +} + +static int +nsslowcert_GetVersionNumber(NSSLOWCERTCertDBHandle *handle) +{ + certDBEntryVersion *versionEntry = NULL; + int version = 0; + + versionEntry = ReadDBVersionEntry(handle); + if (versionEntry == NULL) { + return 0; + } + version = versionEntry->common.version; + DestroyDBEntry((certDBEntry *)versionEntry); + return version; +} + +/* + * Open the certificate database and index databases. Create them if + * they are not there or bad. + */ +static SECStatus +nsslowcert_OpenPermCertDB(NSSLOWCERTCertDBHandle *handle, PRBool readOnly, + const char *appName, const char *prefix, + NSSLOWCERTDBNameFunc namecb, void *cbarg) +{ + SECStatus rv; + int openflags; + char *certdbname; + int version = 0; + + certdbname = (*namecb)(cbarg, CERT_DB_FILE_VERSION); + if (certdbname == NULL) { + return (SECFailure); + } + + openflags = readOnly ? NO_RDONLY : NO_RDWR; + + /* + * first open the permanent file based database. + */ + if (appName) { + handle->permCertDB = rdbopen(appName, prefix, "cert", openflags, NULL); + } else { + handle->permCertDB = dbsopen(certdbname, openflags, 0600, DB_HASH, 0); + } + + /* check for correct version number */ + if (handle->permCertDB) { + version = nsslowcert_GetVersionNumber(handle); + if ((version != CERT_DB_FILE_VERSION) && + !(appName && version == CERT_DB_V7_FILE_VERSION)) { + goto loser; + } + } else if (readOnly) { + /* don't create if readonly */ + /* Try openning a version 7 database */ + handle->permCertDB = nsslowcert_openolddb(namecb, cbarg, 7); + if (!handle->permCertDB) { + goto loser; + } + if (nsslowcert_GetVersionNumber(handle) != 7) { + goto loser; + } + } else { + /* if first open fails, try to create a new DB */ + rv = openNewCertDB(appName, prefix, certdbname, handle, namecb, cbarg); + if (rv == SECWouldBlock) { + /* only the rdb version can fail with wouldblock */ + handle->permCertDB = + rdbopen(appName, prefix, "cert", openflags, NULL); + + /* check for correct version number */ + if (!handle->permCertDB) { + goto loser; + } + version = nsslowcert_GetVersionNumber(handle); + if ((version != CERT_DB_FILE_VERSION) && + !(appName && version == CERT_DB_V7_FILE_VERSION)) { + goto loser; + } + } else if (rv != SECSuccess) { + goto loser; + } + } + + PORT_Free(certdbname); + + return (SECSuccess); + +loser: + + PORT_SetError(SEC_ERROR_BAD_DATABASE); + + if (handle->permCertDB) { + certdb_Close(handle->permCertDB); + handle->permCertDB = 0; + } + + PORT_Free(certdbname); + + return (SECFailure); +} + +/* + * delete all DB records associated with a particular certificate + */ +static SECStatus +DeletePermCert(NSSLOWCERTCertificate *cert) +{ + SECStatus rv; + SECStatus ret; + + ret = SECSuccess; + + rv = DeleteDBCertEntry(cert->dbhandle, &cert->certKey); + if (rv != SECSuccess) { + ret = SECFailure; + } + + rv = RemovePermSubjectNode(cert); + + return (ret); +} + +/* + * Delete a certificate from the permanent database. + */ +SECStatus +nsslowcert_DeletePermCertificate(NSSLOWCERTCertificate *cert) +{ + SECStatus rv; + + nsslowcert_LockDB(cert->dbhandle); + + /* delete the records from the permanent database */ + rv = DeletePermCert(cert); + + /* get rid of dbcert and stuff pointing to it */ + DestroyDBEntry((certDBEntry *)cert->dbEntry); + cert->dbEntry = NULL; + cert->trust = NULL; + + nsslowcert_UnlockDB(cert->dbhandle); + return (rv); +} + +/* + * Traverse all of the entries in the database of a particular type + * call the given function for each one. + */ +SECStatus +nsslowcert_TraverseDBEntries(NSSLOWCERTCertDBHandle *handle, + certDBEntryType type, + SECStatus (*callback)(SECItem *data, SECItem *key, + certDBEntryType type, void *pdata), + void *udata) +{ + DBT data; + DBT key; + SECStatus rv = SECSuccess; + 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); + } + /* here, ret is zero and rv is SECSuccess. + * Below here, ret is a count of successful calls to the callback function. + */ + 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; + /* type should equal keybuf[0]. */ + + rv = (*callback)(&dataitem, &keyitem, type, udata); + if (rv == SECSuccess) { + ++ret; + } + } + } while (certdb_Seq(handle->permCertDB, &key, &data, R_NEXT) == 0); + /* If any callbacks succeeded, or no calls to callbacks were made, + * then report success. Otherwise, report failure. + */ + return (ret ? SECSuccess : rv); +} +/* + * Decode a certificate and enter it into the temporary certificate database. + * Deal with nicknames correctly + * + * This is the private entry point. + */ +static NSSLOWCERTCertificate * +DecodeACert(NSSLOWCERTCertDBHandle *handle, certDBEntryCert *entry) +{ + NSSLOWCERTCertificate *cert = NULL; + + cert = nsslowcert_DecodeDERCertificate(&entry->derCert, entry->nickname); + + if (cert == NULL) { + goto loser; + } + + cert->dbhandle = handle; + cert->dbEntry = entry; + cert->trust = &entry->trust; + + return (cert); + +loser: + return (0); +} + +static NSSLOWCERTTrust * +CreateTrust(void) +{ + NSSLOWCERTTrust *trust = NULL; + + nsslowcert_LockFreeList(); + trust = trustListHead; + if (trust) { + trustListCount--; + trustListHead = trust->next; + trust->next = NULL; + } + PORT_Assert(trustListCount >= 0); + nsslowcert_UnlockFreeList(); + if (trust) { + return trust; + } + + return PORT_ZNew(NSSLOWCERTTrust); +} + +static void +DestroyTrustFreeList(void) +{ + NSSLOWCERTTrust *trust; + + nsslowcert_LockFreeList(); + while (NULL != (trust = trustListHead)) { + trustListCount--; + trustListHead = trust->next; + PORT_Free(trust); + } + PORT_Assert(!trustListCount); + trustListCount = 0; + nsslowcert_UnlockFreeList(); +} + +static NSSLOWCERTTrust * +DecodeTrustEntry(NSSLOWCERTCertDBHandle *handle, certDBEntryCert *entry, + const SECItem *dbKey) +{ + NSSLOWCERTTrust *trust = CreateTrust(); + if (trust == NULL) { + return trust; + } + trust->dbhandle = handle; + trust->dbEntry = entry; + trust->dbKey.data = pkcs11_copyStaticData(dbKey->data, dbKey->len, + trust->dbKeySpace, sizeof(trust->dbKeySpace)); + if (!trust->dbKey.data) { + PORT_Free(trust); + return NULL; + } + trust->dbKey.len = dbKey->len; + + trust->trust = &entry->trust; + trust->derCert = &entry->derCert; + + return (trust); +} + +typedef struct { + PermCertCallback certfunc; + NSSLOWCERTCertDBHandle *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; + NSSLOWCERTCertificate *cert; + PLArenaPool *arena = NULL; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + goto loser; + } + + entry = (certDBEntryCert *)PORT_ArenaAlloc(arena, sizeof(certDBEntryCert)); + if (!entry) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + 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; + + /* note: Entry is 'inheritted'. */ + cert = DecodeACert(mystate->handle, entry); + + rv = (*mystate->certfunc)(cert, dbkey, mystate->data); + + /* arena stored in entry destroyed by nsslowcert_DestroyCertificate */ + nsslowcert_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(NSSLOWCERTCertDBHandle *handle, + SECStatus (*certfunc)(NSSLOWCERTCertificate *cert, + SECItem *k, + void *pdata), + void *udata) +{ + SECStatus rv; + PermCertCallbackState mystate; + + mystate.certfunc = certfunc; + mystate.handle = handle; + mystate.data = udata; + rv = nsslowcert_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 +nsslowcert_TraversePermCerts(NSSLOWCERTCertDBHandle *handle, + SECStatus (*certfunc)(NSSLOWCERTCertificate *cert, SECItem *k, + void *pdata), + void *udata) +{ + SECStatus rv; + + nsslowcert_LockDB(handle); + rv = TraversePermCertsNoLocking(handle, certfunc, udata); + nsslowcert_UnlockDB(handle); + + return (rv); +} + +/* + * Close the database + */ +void +nsslowcert_ClosePermCertDB(NSSLOWCERTCertDBHandle *handle) +{ + if (handle) { + if (handle->permCertDB) { + certdb_Close(handle->permCertDB); + handle->permCertDB = NULL; + } + if (handle->dbMon) { + PZ_DestroyMonitor(handle->dbMon); + handle->dbMon = NULL; + } + PORT_Free(handle); + } + return; +} + +/* + * Get the trust attributes from a certificate + */ +SECStatus +nsslowcert_GetCertTrust(NSSLOWCERTCertificate *cert, NSSLOWCERTCertTrust *trust) +{ + SECStatus rv; + + nsslowcert_LockCertTrust(cert); + + if (cert->trust == NULL) { + rv = SECFailure; + } else { + *trust = *cert->trust; + rv = SECSuccess; + } + + nsslowcert_UnlockCertTrust(cert); + return (rv); +} + +/* + * Change the trust attributes of a certificate and make them permanent + * in the database. + */ +SECStatus +nsslowcert_ChangeCertTrust(NSSLOWCERTCertDBHandle *handle, + NSSLOWCERTCertificate *cert, NSSLOWCERTCertTrust *trust) +{ + certDBEntryCert *entry; + int rv; + SECStatus ret; + + nsslowcert_LockDB(handle); + nsslowcert_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: + nsslowcert_UnlockCertTrust(cert); + nsslowcert_UnlockDB(handle); + return (ret); +} + +static SECStatus +nsslowcert_UpdatePermCert(NSSLOWCERTCertDBHandle *dbhandle, + NSSLOWCERTCertificate *cert, char *nickname, NSSLOWCERTCertTrust *trust) +{ + char *oldnn; + certDBEntryCert *entry; + PRBool conflict; + SECStatus ret; + + PORT_Assert(!cert->dbEntry); + + /* don't add a conflicting nickname */ + conflict = nsslowcert_CertNicknameConflict(nickname, &cert->derSubject, + dbhandle); + if (conflict) { + ret = SECFailure; + goto done; + } + + /* save old nickname so that we can delete it */ + oldnn = cert->nickname; + + entry = AddCertToPermDB(dbhandle, cert, nickname, trust); + + if (entry == NULL) { + ret = SECFailure; + goto done; + } + + pkcs11_freeNickname(oldnn, cert->nicknameSpace); + + cert->nickname = (entry->nickname) ? pkcs11_copyNickname(entry->nickname, + cert->nicknameSpace, sizeof(cert->nicknameSpace)) + : NULL; + cert->trust = &entry->trust; + cert->dbEntry = entry; + + ret = SECSuccess; +done: + return (ret); +} + +SECStatus +nsslowcert_AddPermCert(NSSLOWCERTCertDBHandle *dbhandle, + NSSLOWCERTCertificate *cert, char *nickname, NSSLOWCERTCertTrust *trust) +{ + SECStatus ret; + + nsslowcert_LockDB(dbhandle); + + ret = nsslowcert_UpdatePermCert(dbhandle, cert, nickname, trust); + + nsslowcert_UnlockDB(dbhandle); + return (ret); +} + +/* + * Open the certificate database and index databases. Create them if + * they are not there or bad. + */ +SECStatus +nsslowcert_OpenCertDB(NSSLOWCERTCertDBHandle *handle, PRBool readOnly, + const char *appName, const char *prefix, + NSSLOWCERTDBNameFunc namecb, void *cbarg, PRBool openVolatile) +{ + int rv; + + certdb_InitDBLock(handle); + + handle->dbMon = PZ_NewMonitor(nssILockCertDB); + PORT_Assert(handle->dbMon != NULL); + handle->dbVerify = PR_FALSE; + + rv = nsslowcert_OpenPermCertDB(handle, readOnly, appName, prefix, + namecb, cbarg); + if (rv) { + goto loser; + } + + return (SECSuccess); + +loser: + if (handle->dbMon) { + PZ_DestroyMonitor(handle->dbMon); + handle->dbMon = NULL; + } + PORT_SetError(SEC_ERROR_BAD_DATABASE); + return (SECFailure); +} + +PRBool +nsslowcert_needDBVerify(NSSLOWCERTCertDBHandle *handle) +{ + if (!handle) + return PR_FALSE; + return handle->dbVerify; +} + +void +nsslowcert_setDBVerify(NSSLOWCERTCertDBHandle *handle, PRBool value) +{ + handle->dbVerify = value; +} + +/* + * Lookup a certificate in the databases. + */ +static NSSLOWCERTCertificate * +FindCertByKey(NSSLOWCERTCertDBHandle *handle, const SECItem *certKey, PRBool lockdb) +{ + NSSLOWCERTCertificate *cert = NULL; + certDBEntryCert *entry; + PRBool locked = PR_FALSE; + + if (lockdb) { + locked = PR_TRUE; + nsslowcert_LockDB(handle); + } + + /* find in perm database */ + entry = ReadDBCertEntry(handle, certKey); + + if (entry == NULL) { + goto loser; + } + + /* inherit entry */ + cert = DecodeACert(handle, entry); + +loser: + if (cert == NULL) { + if (entry) { + DestroyDBEntry((certDBEntry *)entry); + } + } + + if (locked) { + nsslowcert_UnlockDB(handle); + } + + return (cert); +} + +/* + * Lookup a certificate in the databases. + */ +static NSSLOWCERTTrust * +FindTrustByKey(NSSLOWCERTCertDBHandle *handle, const SECItem *certKey, PRBool lockdb) +{ + NSSLOWCERTTrust *trust = NULL; + certDBEntryCert *entry; + PRBool locked = PR_FALSE; + + if (lockdb) { + locked = PR_TRUE; + nsslowcert_LockDB(handle); + } + + /* find in perm database */ + entry = ReadDBCertEntry(handle, certKey); + + if (entry == NULL) { + goto loser; + } + + if (!nsslowcert_hasTrust(&entry->trust)) { + goto loser; + } + + /* inherit entry */ + trust = DecodeTrustEntry(handle, entry, certKey); + +loser: + if (trust == NULL) { + if (entry) { + DestroyDBEntry((certDBEntry *)entry); + } + } + + if (locked) { + nsslowcert_UnlockDB(handle); + } + + return (trust); +} + +/* + * Lookup a certificate in the databases without locking + */ +NSSLOWCERTCertificate * +nsslowcert_FindCertByKey(NSSLOWCERTCertDBHandle *handle, const SECItem *certKey) +{ + return (FindCertByKey(handle, certKey, PR_FALSE)); +} + +/* + * Lookup a trust object in the databases without locking + */ +NSSLOWCERTTrust * +nsslowcert_FindTrustByKey(NSSLOWCERTCertDBHandle *handle, const SECItem *certKey) +{ + return (FindTrustByKey(handle, certKey, PR_FALSE)); +} + +/* + * Generate a key from an issuerAndSerialNumber, and find the + * associated cert in the database. + */ +NSSLOWCERTCertificate * +nsslowcert_FindCertByIssuerAndSN(NSSLOWCERTCertDBHandle *handle, NSSLOWCERTIssuerAndSN *issuerAndSN) +{ + SECItem certKey; + SECItem *sn = &issuerAndSN->serialNumber; + SECItem *issuer = &issuerAndSN->derIssuer; + NSSLOWCERTCertificate *cert; + int data_len = sn->len; + int index = 0; + + /* automatically detect DER encoded serial numbers and remove the der + * encoding since the database expects unencoded data. + * if it's DER encoded, there must be at least 3 bytes, tag, len, data */ + if ((sn->len >= 3) && (sn->data[0] == 0x2)) { + /* remove the der encoding of the serial number before generating the + * key.. */ + int data_left = sn->len - 2; + data_len = sn->data[1]; + index = 2; + + /* extended length ? (not very likely for a serial number) */ + if (data_len & 0x80) { + int len_count = data_len & 0x7f; + + data_len = 0; + data_left -= len_count; + if (data_left > 0) { + while (len_count--) { + data_len = (data_len << 8) | sn->data[index++]; + } + } + } + /* XXX leaving any leading zeros on the serial number for backwards + * compatibility + */ + /* not a valid der, must be just an unlucky serial number value */ + if (data_len != data_left) { + data_len = sn->len; + index = 0; + } + } + + certKey.type = 0; + certKey.data = (unsigned char *)PORT_Alloc(sn->len + issuer->len); + certKey.len = data_len + issuer->len; + + if (certKey.data == NULL) { + return (0); + } + + /* first try the serial number as hand-decoded above*/ + /* copy the serialNumber */ + PORT_Memcpy(certKey.data, &sn->data[index], data_len); + + /* copy the issuer */ + PORT_Memcpy(&certKey.data[data_len], issuer->data, issuer->len); + + cert = nsslowcert_FindCertByKey(handle, &certKey); + if (cert) { + PORT_Free(certKey.data); + return (cert); + } + + /* didn't find it, try by der encoded serial number */ + /* copy the serialNumber */ + PORT_Memcpy(certKey.data, sn->data, sn->len); + + /* copy the issuer */ + PORT_Memcpy(&certKey.data[sn->len], issuer->data, issuer->len); + certKey.len = sn->len + issuer->len; + + cert = nsslowcert_FindCertByKey(handle, &certKey); + + PORT_Free(certKey.data); + + return (cert); +} + +/* + * Generate a key from an issuerAndSerialNumber, and find the + * associated cert in the database. + */ +NSSLOWCERTTrust * +nsslowcert_FindTrustByIssuerAndSN(NSSLOWCERTCertDBHandle *handle, + NSSLOWCERTIssuerAndSN *issuerAndSN) +{ + SECItem certKey; + SECItem *sn = &issuerAndSN->serialNumber; + SECItem *issuer = &issuerAndSN->derIssuer; + NSSLOWCERTTrust *trust; + unsigned char keyBuf[512]; + int data_len = sn->len; + int index = 0; + int len; + + /* automatically detect DER encoded serial numbers and remove the der + * encoding since the database expects unencoded data. + * if it's DER encoded, there must be at least 3 bytes, tag, len, data */ + if ((sn->len >= 3) && (sn->data[0] == 0x2)) { + /* remove the der encoding of the serial number before generating the + * key.. */ + int data_left = sn->len - 2; + data_len = sn->data[1]; + index = 2; + + /* extended length ? (not very likely for a serial number) */ + if (data_len & 0x80) { + int len_count = data_len & 0x7f; + + data_len = 0; + data_left -= len_count; + if (data_left > 0) { + while (len_count--) { + data_len = (data_len << 8) | sn->data[index++]; + } + } + } + /* XXX leaving any leading zeros on the serial number for backwards + * compatibility + */ + /* not a valid der, must be just an unlucky serial number value */ + if (data_len != data_left) { + data_len = sn->len; + index = 0; + } + } + + certKey.type = 0; + certKey.len = data_len + issuer->len; + len = sn->len + issuer->len; + if (len > sizeof(keyBuf)) { + certKey.data = (unsigned char *)PORT_Alloc(len); + } else { + certKey.data = keyBuf; + } + + if (certKey.data == NULL) { + return (0); + } + + /* first try the serial number as hand-decoded above*/ + /* copy the serialNumber */ + PORT_Memcpy(certKey.data, &sn->data[index], data_len); + + /* copy the issuer */ + PORT_Memcpy(&certKey.data[data_len], issuer->data, issuer->len); + + trust = nsslowcert_FindTrustByKey(handle, &certKey); + if (trust) { + pkcs11_freeStaticData(certKey.data, keyBuf); + return (trust); + } + + if (index == 0) { + pkcs11_freeStaticData(certKey.data, keyBuf); + return NULL; + } + + /* didn't find it, try by der encoded serial number */ + /* copy the serialNumber */ + PORT_Memcpy(certKey.data, sn->data, sn->len); + + /* copy the issuer */ + PORT_Memcpy(&certKey.data[sn->len], issuer->data, issuer->len); + certKey.len = sn->len + issuer->len; + + trust = nsslowcert_FindTrustByKey(handle, &certKey); + + pkcs11_freeStaticData(certKey.data, keyBuf); + + return (trust); +} + +/* + * look for the given DER certificate in the database + */ +NSSLOWCERTCertificate * +nsslowcert_FindCertByDERCert(NSSLOWCERTCertDBHandle *handle, SECItem *derCert) +{ + PLArenaPool *arena; + SECItem certKey; + SECStatus rv; + NSSLOWCERTCertificate *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 = nsslowcert_KeyFromDERCert(arena, derCert, &certKey); + if (rv != SECSuccess) { + goto loser; + } + + /* find the certificate */ + cert = nsslowcert_FindCertByKey(handle, &certKey); + +loser: + PORT_FreeArena(arena, PR_FALSE); + return (cert); +} + +static void +DestroyCertificate(NSSLOWCERTCertificate *cert, PRBool lockdb) +{ + int refCount; + NSSLOWCERTCertDBHandle *handle; + + if (cert) { + + handle = cert->dbhandle; + + /* + * handle may be NULL, for example if the cert was created with + * nsslowcert_DecodeDERCertificate. + */ + if (lockdb && handle) { + nsslowcert_LockDB(handle); + } + + nsslowcert_LockCertRefCount(cert); + PORT_Assert(cert->referenceCount > 0); + refCount = --cert->referenceCount; + nsslowcert_UnlockCertRefCount(cert); + + if (refCount == 0) { + certDBEntryCert *entry = cert->dbEntry; + + if (entry) { + DestroyDBEntry((certDBEntry *)entry); + } + + pkcs11_freeNickname(cert->nickname, cert->nicknameSpace); + pkcs11_freeNickname(cert->emailAddr, cert->emailAddrSpace); + pkcs11_freeStaticData(cert->certKey.data, cert->certKeySpace); + cert->certKey.data = NULL; + cert->nickname = NULL; + + /* zero cert before freeing. Any stale references to this cert + * after this point will probably cause an exception. */ + PORT_Memset(cert, 0, sizeof *cert); + + /* use reflock to protect the free list */ + nsslowcert_LockFreeList(); + if (certListCount > MAX_CERT_LIST_COUNT) { + PORT_Free(cert); + } else { + certListCount++; + cert->next = certListHead; + certListHead = cert; + } + nsslowcert_UnlockFreeList(); + cert = NULL; + } + if (lockdb && handle) { + nsslowcert_UnlockDB(handle); + } + } + + return; +} + +NSSLOWCERTCertificate * +nsslowcert_CreateCert(void) +{ + NSSLOWCERTCertificate *cert; + nsslowcert_LockFreeList(); + cert = certListHead; + if (cert) { + certListHead = cert->next; + certListCount--; + } + PORT_Assert(certListCount >= 0); + nsslowcert_UnlockFreeList(); + if (cert) { + return cert; + } + return PORT_ZNew(NSSLOWCERTCertificate); +} + +static void +DestroyCertFreeList(void) +{ + NSSLOWCERTCertificate *cert; + + nsslowcert_LockFreeList(); + while (NULL != (cert = certListHead)) { + certListCount--; + certListHead = cert->next; + PORT_Free(cert); + } + PORT_Assert(!certListCount); + certListCount = 0; + nsslowcert_UnlockFreeList(); +} + +void +nsslowcert_DestroyTrust(NSSLOWCERTTrust *trust) +{ + certDBEntryCert *entry = trust->dbEntry; + + if (entry) { + DestroyDBEntry((certDBEntry *)entry); + } + pkcs11_freeStaticData(trust->dbKey.data, trust->dbKeySpace); + PORT_Memset(trust, 0, sizeof(*trust)); + + nsslowcert_LockFreeList(); + if (trustListCount > MAX_TRUST_LIST_COUNT) { + PORT_Free(trust); + } else { + trustListCount++; + trust->next = trustListHead; + trustListHead = trust; + } + nsslowcert_UnlockFreeList(); + + return; +} + +void +nsslowcert_DestroyCertificate(NSSLOWCERTCertificate *cert) +{ + DestroyCertificate(cert, PR_TRUE); + return; +} + +static void +nsslowcert_DestroyCertificateNoLocking(NSSLOWCERTCertificate *cert) +{ + DestroyCertificate(cert, PR_FALSE); + return; +} + +/* + * Lookup a CRL in the databases. We mirror the same fast caching data base + * caching stuff used by certificates....? + */ +certDBEntryRevocation * +nsslowcert_FindCrlByKey(NSSLOWCERTCertDBHandle *handle, + SECItem *crlKey, PRBool isKRL) +{ + SECItem keyitem; + SECStatus rv; + PLArenaPool *arena = NULL; + certDBEntryRevocation *entry = NULL; + certDBEntryType crlType = isKRL ? certDBEntryTypeKeyRevocation + : certDBEntryTypeRevocation; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + goto loser; + } + + rv = EncodeDBGenericKey(crlKey, arena, &keyitem, crlType); + if (rv != SECSuccess) { + goto loser; + } + + /* find in perm database */ + entry = ReadDBCrlEntry(handle, crlKey, crlType); + + if (entry == NULL) { + goto loser; + } + +loser: + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + + return entry; +} + +/* + * replace the existing URL in the data base with a new one + */ +static SECStatus +nsslowcert_UpdateCrl(NSSLOWCERTCertDBHandle *handle, SECItem *derCrl, + SECItem *crlKey, char *url, PRBool isKRL) +{ + SECStatus rv = SECFailure; + certDBEntryRevocation *entry = NULL; + certDBEntryType crlType = isKRL ? certDBEntryTypeKeyRevocation + : certDBEntryTypeRevocation; + DeleteDBCrlEntry(handle, crlKey, crlType); + + /* Write the new entry into the data base */ + entry = NewDBCrlEntry(derCrl, url, crlType, 0); + if (entry == NULL) + goto done; + + rv = WriteDBCrlEntry(handle, entry, crlKey); + if (rv != SECSuccess) + goto done; + +done: + if (entry) { + DestroyDBEntry((certDBEntry *)entry); + } + return rv; +} + +SECStatus +nsslowcert_AddCrl(NSSLOWCERTCertDBHandle *handle, SECItem *derCrl, + SECItem *crlKey, char *url, PRBool isKRL) +{ + SECStatus rv; + + rv = nsslowcert_UpdateCrl(handle, derCrl, crlKey, url, isKRL); + + return rv; +} + +SECStatus +nsslowcert_DeletePermCRL(NSSLOWCERTCertDBHandle *handle, const SECItem *derName, + PRBool isKRL) +{ + SECStatus rv; + certDBEntryType crlType = isKRL ? certDBEntryTypeKeyRevocation + : certDBEntryTypeRevocation; + + rv = DeleteDBCrlEntry(handle, derName, crlType); + if (rv != SECSuccess) + goto done; + +done: + return rv; +} + +PRBool +nsslowcert_hasTrust(NSSLOWCERTCertTrust *trust) +{ + if (trust == NULL) { + return PR_FALSE; + } + /* if we only have CERTDB__USER and CERTDB_TRUSTED_UNKNOWN bits, then + * we don't have a trust record. */ + return !(((trust->sslFlags & ~(CERTDB_USER | CERTDB_TRUSTED_UNKNOWN)) == 0) && + ((trust->emailFlags & ~(CERTDB_USER | CERTDB_TRUSTED_UNKNOWN)) == 0) && + ((trust->objectSigningFlags & ~(CERTDB_USER | CERTDB_TRUSTED_UNKNOWN)) == 0)); +} + +/* + * 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. + */ +static SECStatus +nsslowcert_UpdateSMimeProfile(NSSLOWCERTCertDBHandle *dbhandle, + char *emailAddr, SECItem *derSubject, SECItem *emailProfile, + SECItem *profileTime) +{ + certDBEntrySMime *entry = NULL; + SECStatus rv = SECFailure; + ; + + /* find our existing entry */ + entry = nsslowcert_ReadDBSMimeEntry(dbhandle, emailAddr); + + if (entry) { + /* keep our old db entry consistant for old applications. */ + if (!SECITEM_ItemsAreEqual(derSubject, &entry->subjectName)) { + nsslowcert_UpdateSubjectEmailAddr(dbhandle, &entry->subjectName, + emailAddr, nsslowcert_remove); + } + DestroyDBEntry((certDBEntry *)entry); + entry = NULL; + } + + /* now save the entry */ + entry = NewDBSMimeEntry(emailAddr, derSubject, emailProfile, + profileTime, 0); + if (entry == NULL) { + rv = SECFailure; + goto loser; + } + + nsslowcert_LockDB(dbhandle); + + rv = DeleteDBSMimeEntry(dbhandle, emailAddr); + /* if delete fails, try to write new entry anyway... */ + + /* link subject entry back here */ + rv = nsslowcert_UpdateSubjectEmailAddr(dbhandle, derSubject, emailAddr, + nsslowcert_add); + if (rv != SECSuccess) { + nsslowcert_UnlockDB(dbhandle); + goto loser; + } + + rv = WriteDBSMimeEntry(dbhandle, entry); + if (rv != SECSuccess) { + nsslowcert_UnlockDB(dbhandle); + goto loser; + } + + nsslowcert_UnlockDB(dbhandle); + + rv = SECSuccess; + +loser: + if (entry) { + DestroyDBEntry((certDBEntry *)entry); + } + return (rv); +} + +SECStatus +nsslowcert_SaveSMimeProfile(NSSLOWCERTCertDBHandle *dbhandle, char *emailAddr, + SECItem *derSubject, SECItem *emailProfile, SECItem *profileTime) +{ + SECStatus rv = SECFailure; + ; + + rv = nsslowcert_UpdateSMimeProfile(dbhandle, emailAddr, + derSubject, emailProfile, profileTime); + + return (rv); +} + +void +nsslowcert_DestroyFreeLists(void) +{ + if (freeListLock == NULL) { + return; + } + DestroyCertEntryFreeList(); + DestroyTrustFreeList(); + DestroyCertFreeList(); + SKIP_AFTER_FORK(PZ_DestroyLock(freeListLock)); + freeListLock = NULL; +} + +void +nsslowcert_DestroyGlobalLocks(void) +{ + if (dbLock) { + SKIP_AFTER_FORK(PZ_DestroyLock(dbLock)); + dbLock = NULL; + } + if (certRefCountLock) { + SKIP_AFTER_FORK(PZ_DestroyLock(certRefCountLock)); + certRefCountLock = NULL; + } + if (certTrustLock) { + SKIP_AFTER_FORK(PZ_DestroyLock(certTrustLock)); + certTrustLock = NULL; + } +} + +certDBEntry * +nsslowcert_DecodeAnyDBEntry(SECItem *dbData, const SECItem *dbKey, + certDBEntryType entryType, void *pdata) +{ + PLArenaPool *arena = NULL; + certDBEntry *entry; + SECStatus rv; + SECItem dbEntry; + + if ((dbData->len < SEC_DB_ENTRY_HEADER_LEN) || (dbKey->len == 0)) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + goto loser; + } + dbEntry.data = &dbData->data[SEC_DB_ENTRY_HEADER_LEN]; + dbEntry.len = dbData->len - SEC_DB_ENTRY_HEADER_LEN; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + goto loser; + } + entry = PORT_ArenaZNew(arena, certDBEntry); + if (!entry) + goto loser; + + entry->common.version = (unsigned int)dbData->data[0]; + entry->common.flags = (unsigned int)dbData->data[2]; + entry->common.type = entryType; + entry->common.arena = arena; + + switch (entryType) { + case certDBEntryTypeContentVersion: /* This type appears to be unused */ + case certDBEntryTypeVersion: /* This type has only the common hdr */ + rv = SECSuccess; + break; + + case certDBEntryTypeSubject: + rv = DecodeDBSubjectEntry(&entry->subject, &dbEntry, dbKey); + break; + + case certDBEntryTypeNickname: + rv = DecodeDBNicknameEntry(&entry->nickname, &dbEntry, + (char *)dbKey->data); + break; + + /* smime profiles need entries created after the certs have + * been imported, loop over them in a second run */ + case certDBEntryTypeSMimeProfile: + rv = DecodeDBSMimeEntry(&entry->smime, &dbEntry, (char *)dbKey->data); + break; + + case certDBEntryTypeCert: + rv = DecodeDBCertEntry(&entry->cert, &dbEntry); + break; + + case certDBEntryTypeKeyRevocation: + case certDBEntryTypeRevocation: + rv = DecodeDBCrlEntry(&entry->revocation, &dbEntry); + break; + + default: + PORT_SetError(SEC_ERROR_INVALID_ARGS); + rv = SECFailure; + } + + if (rv == SECSuccess) + return entry; + +loser: + if (arena) + PORT_FreeArena(arena, PR_FALSE); + return NULL; +} |