/* 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) { PRStatus prstat; prstat = PZ_ExitMonitor(handle->dbMon); PORT_Assert(prstat == PR_SUCCESS); return; } /* * 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) { PRStatus prstat; PORT_Assert(certRefCountLock != NULL); prstat = PZ_Unlock(certRefCountLock); PORT_Assert(prstat == PR_SUCCESS); return; } /* * 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) { PRStatus prstat; PORT_Assert(certTrustLock != NULL); prstat = PZ_Unlock(certTrustLock); PORT_Assert(prstat == PR_SUCCESS); return; } /* * 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) { PRStatus prstat = PR_SUCCESS; PORT_Assert(freeListLock != NULL); SKIP_AFTER_FORK(prstat = PZ_Unlock(freeListLock)); PORT_Assert(prstat == PR_SUCCESS); return; } 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) { PRStatus prstat; int ret; PORT_Assert(dbLock != NULL); PZ_Lock(dbLock); ret = (* db->get)(db, key, data, flags); prstat = PZ_Unlock(dbLock); return(ret); } static int certdb_Put(DB *db, DBT *key, DBT *data, unsigned int flags) { PRStatus prstat; int ret = 0; PORT_Assert(dbLock != NULL); PZ_Lock(dbLock); ret = (* db->put)(db, key, data, flags); prstat = PZ_Unlock(dbLock); return(ret); } static int certdb_Sync(DB *db, unsigned int flags) { PRStatus prstat; int ret; PORT_Assert(dbLock != NULL); PZ_Lock(dbLock); ret = (* db->sync)(db, flags); prstat = 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) { PRStatus prstat; int ret; PORT_Assert(dbLock != NULL); PZ_Lock(dbLock); ret = (* db->del)(db, key, flags); prstat = 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) { PRStatus prstat; int ret; PORT_Assert(dbLock != NULL); PZ_Lock(dbLock); ret = (* db->seq)(db, key, data, flags); prstat = PZ_Unlock(dbLock); return(ret); } static void certdb_Close(DB *db) { PRStatus prstat = PR_SUCCESS; PORT_Assert(dbLock != NULL); SKIP_AFTER_FORK(PZ_Lock(dbLock)); (* db->close)(db); SKIP_AFTER_FORK(prstat = 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; } /* 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 ) { 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); } if ( entry->optionsDate.len ) { entry->optionsDate.data = (unsigned char *)PORT_ArenaAlloc(entry->common.arena, entry->optionsDate.len); if ( entry->optionsDate.data == NULL ) { PORT_SetError(SEC_ERROR_NO_MEMORY); goto loser; } PORT_Memcpy(entry->optionsDate.data, &dbentry->data[DB_SMIME_ENTRY_HEADER_LEN + entry->subjectName.len + entry->smimeOptions.len], entry->optionsDate.len); } /* both options and options date must either exist or not exist */ if ( ( ( entry->optionsDate.len == 0 ) || ( entry->smimeOptions.len == 0 ) ) && entry->smimeOptions.len != entry->optionsDate.len ) { PORT_SetError(SEC_ERROR_BAD_DATABASE); goto loser; } entry->emailAddr = (char *)PORT_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; SECItem dbkey; SECItem dbentry; SECStatus rv; arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if ( arena == NULL ) { PORT_SetError(SEC_ERROR_NO_MEMORY); goto loser; } tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if ( tmparena == NULL ) { PORT_SetError(SEC_ERROR_NO_MEMORY); goto loser; } entry = (certDBEntrySMime *)PORT_ArenaAlloc(arena, sizeof(certDBEntrySMime)); if ( entry == NULL ) { PORT_SetError(SEC_ERROR_NO_MEMORY); goto loser; } entry->common.arena = arena; entry->common.type = certDBEntryTypeSMimeProfile; rv = EncodeDBSMimeKey(emailAddr, tmparena, &dbkey); if ( rv != SECSuccess ) { goto loser; } rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena); if ( rv == SECFailure ) { goto loser; } /* is record long enough for header? */ if ( dbentry.len < DB_SMIME_ENTRY_HEADER_LEN ) { PORT_SetError(SEC_ERROR_BAD_DATABASE); goto loser; } rv = DecodeDBSMimeEntry(entry, &dbentry, emailAddr); if ( rv != SECSuccess ) { goto loser; } PORT_FreeArena(tmparena, PR_FALSE); return(entry); loser: if ( tmparena ) { PORT_FreeArena(tmparena, PR_FALSE); } if ( arena ) { PORT_FreeArena(arena, PR_FALSE); } return(NULL); } /* * Encode a SMIME entry into byte stream suitable for * the database */ static SECStatus WriteDBSMimeEntry(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_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++ ) { 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; unsigned int eaddrlen; arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if ( arena == NULL ) { PORT_SetError(SEC_ERROR_NO_MEMORY); goto loser; } entry = (certDBEntrySubject *)PORT_ArenaAlloc(arena, sizeof(certDBEntrySubject)); if ( entry == NULL ) { PORT_SetError(SEC_ERROR_NO_MEMORY); goto loser; } /* init common fields */ entry->common.arena = arena; entry->common.type = certDBEntryTypeSubject; entry->common.version = CERT_DB_FILE_VERSION; entry->common.flags = flags; /* copy the subject */ rv = SECITEM_CopyItem(arena, &entry->derSubject, derSubject); if ( rv != SECSuccess ) { goto loser; } entry->ncerts = 1; 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; } eaddrlen = PORT_Strlen(emailAddr) + 1; 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) { PLArenaPool *arena = NULL; PLArenaPool *tmparena = NULL; certDBEntrySubject *entry; SECItem dbkey; SECItem dbentry; SECStatus rv; arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if ( arena == NULL ) { PORT_SetError(SEC_ERROR_NO_MEMORY); goto loser; } tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if ( tmparena == NULL ) { PORT_SetError(SEC_ERROR_NO_MEMORY); goto loser; } entry = (certDBEntrySubject *)PORT_ArenaAlloc(arena, sizeof(certDBEntrySubject)); if ( entry == NULL ) { PORT_SetError(SEC_ERROR_NO_MEMORY); goto loser; } entry->common.arena = arena; entry->common.type = certDBEntryTypeSubject; rv = EncodeDBSubjectKey(derSubject, tmparena, &dbkey); if ( rv != SECSuccess ) { goto loser; } rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena); if ( rv == SECFailure ) { goto loser; } rv = DecodeDBSubjectEntry(entry, &dbentry, derSubject); if ( rv == SECFailure ) { goto loser; } PORT_FreeArena(tmparena, PR_FALSE); return(entry); loser: if ( tmparena ) { PORT_FreeArena(tmparena, PR_FALSE); } if ( arena ) { PORT_FreeArena(arena, PR_FALSE); } return(NULL); } /* * Encode a subject name entry into byte stream suitable for * the database */ static SECStatus WriteDBSubjectEntry(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; } return(SECSuccess); loser: 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 ) { rv = DeleteDBCertEntry(handle, &cert->certKey); } if ( ( state > 1 ) && donnentry ) { rv = DeleteDBNicknameEntry(handle, nickname); } if ( state > 2 ) { rv = DeleteDBSubjectEntry(handle, &cert->derSubject); } if ( certEntry ) { DestroyDBEntry((certDBEntry *)certEntry); } if ( nicknameEntry ) { DestroyDBEntry((certDBEntry *)nicknameEntry); } if ( subjectEntry ) { DestroyDBEntry((certDBEntry *)subjectEntry); } return(NULL); } /* 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; SECStatus rv; /* * Sequence through the old database and copy all of the entries * to the new database. Subject name entries will have the new * fields inserted into them (with zero length). */ ret = (* updatedb->seq)(updatedb, &key, &data, R_FIRST); if ( ret ) { return(SECFailure); } do { buf = (unsigned char *)data.data; if ( data.size >= 3 ) { if ( buf[0] == 6 ) { /* version number */ type = (certDBEntryType)buf[1]; if ( type == certDBEntryTypeSubject ) { /* expando subjecto entrieo */ tmpbuf = (unsigned char *)PORT_Alloc(data.size + 4); if ( tmpbuf ) { /* copy header stuff */ PORT_Memcpy(tmpbuf, buf, SEC_DB_ENTRY_HEADER_LEN + 2); /* insert 4 more bytes of zero'd header */ PORT_Memset(&tmpbuf[SEC_DB_ENTRY_HEADER_LEN + 2], 0, 4); /* copy rest of the data */ PORT_Memcpy(&tmpbuf[SEC_DB_ENTRY_HEADER_LEN + 6], &buf[SEC_DB_ENTRY_HEADER_LEN + 2], data.size - (SEC_DB_ENTRY_HEADER_LEN + 2)); data.data = (void *)tmpbuf; data.size += 4; buf = tmpbuf; } } else if ( type == certDBEntryTypeCert ) { /* expando certo entrieo */ tmpbuf = (unsigned char *)PORT_Alloc(data.size + 3); if ( tmpbuf ) { /* copy header stuff */ PORT_Memcpy(tmpbuf, buf, SEC_DB_ENTRY_HEADER_LEN); /* copy trust flage, setting msb's to 0 */ tmpbuf[SEC_DB_ENTRY_HEADER_LEN] = 0; tmpbuf[SEC_DB_ENTRY_HEADER_LEN+1] = buf[SEC_DB_ENTRY_HEADER_LEN]; tmpbuf[SEC_DB_ENTRY_HEADER_LEN+2] = 0; tmpbuf[SEC_DB_ENTRY_HEADER_LEN+3] = buf[SEC_DB_ENTRY_HEADER_LEN+1]; tmpbuf[SEC_DB_ENTRY_HEADER_LEN+4] = 0; tmpbuf[SEC_DB_ENTRY_HEADER_LEN+5] = buf[SEC_DB_ENTRY_HEADER_LEN+2]; /* copy rest of the data */ PORT_Memcpy(&tmpbuf[SEC_DB_ENTRY_HEADER_LEN + 6], &buf[SEC_DB_ENTRY_HEADER_LEN + 3], data.size - (SEC_DB_ENTRY_HEADER_LEN + 3)); data.data = (void *)tmpbuf; data.size += 3; buf = tmpbuf; } } /* update the record version number */ buf[0] = CERT_DB_FILE_VERSION; /* copy to the new database */ ret = certdb_Put(handle->permCertDB, &key, &data, 0); if ( tmpbuf ) { PORT_Free(tmpbuf); tmpbuf = NULL; } } } } while ( (* updatedb->seq)(updatedb, &key, &data, R_NEXT) == 0 ); ret = certdb_Sync(handle->permCertDB, 0); ret = (* updatedb->seq)(updatedb, &key, &data, R_FIRST); if ( ret ) { return(SECFailure); } do { buf = (unsigned char *)data.data; if ( data.size >= 3 ) { if ( buf[0] == CERT_DB_FILE_VERSION ) { /* version number */ type = (certDBEntryType)buf[1]; if ( type == certDBEntryTypeNickname ) { nickname = &((char *)key.data)[1]; /* get the matching nickname entry in the new DB */ nnEntry = ReadDBNicknameEntry(handle, nickname); if ( nnEntry == NULL ) { goto endloop; } /* find the subject entry pointed to by nickname */ subjectEntry = ReadDBSubjectEntry(handle, &nnEntry->subjectName); if ( subjectEntry == NULL ) { goto endloop; } subjectEntry->nickname = (char *)PORT_ArenaAlloc(subjectEntry->common.arena, key.size - 1); if ( subjectEntry->nickname ) { PORT_Memcpy(subjectEntry->nickname, nickname, key.size - 1); rv = WriteDBSubjectEntry(handle, subjectEntry); } } else if ( type == certDBEntryTypeSMimeProfile ) { emailAddr = &((char *)key.data)[1]; /* get the matching smime entry in the new DB */ emailEntry = 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; rv = WriteDBSubjectEntry(handle, subjectEntry); } } } endloop: if ( subjectEntry ) { DestroyDBEntry((certDBEntry *)subjectEntry); subjectEntry = NULL; } if ( nnEntry ) { DestroyDBEntry((certDBEntry *)nnEntry); nnEntry = NULL; } if ( emailEntry ) { DestroyDBEntry((certDBEntry *)emailEntry); emailEntry = NULL; } } } } while ( (* updatedb->seq)(updatedb, &key, &data, R_NEXT) == 0 ); ret = certdb_Sync(handle->permCertDB, 0); (* updatedb->close)(updatedb); return(SECSuccess); } static SECStatus updateV5Callback(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; SECStatus rv; updatehandle.permCertDB = updatedb; updatehandle.dbMon = PZ_NewMonitor(nssILockCertDB); updatehandle.dbVerify = 0; updatehandle.ref = 1; /* prevent premature close */ rv = 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; PLArenaPool *arena = NULL; NSSLOWCERTCertificate *cert; ret = (* updatedb->seq)(updatedb, &key, &data, R_FIRST); if ( ret ) { return(SECFailure); } arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if (arena == NULL) { return(SECFailure); } do { if ( data.size != 1 ) { /* skip version number */ /* decode the old DB entry */ entry = (certDBEntryCert *) DecodeV4DBCertEntry((unsigned char*)data.data, data.size); 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 ); PORT_FreeArena(arena, PR_FALSE); (* 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; } 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)); 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; DBT key; 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; } key.data = keyitem.data; key.size = keyitem.len; /* 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; } return !((trust->sslFlags & CERTDB_TRUSTED_UNKNOWN) && (trust->emailFlags & CERTDB_TRUSTED_UNKNOWN) && (trust->objectSigningFlags & CERTDB_TRUSTED_UNKNOWN)); } /* * 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; }