fixup commit for tag 'NSS_3_3_BETA2' NSS_3_3_BRANCH NSS_3_3_BETA2
authorcvs2hg
Wed, 04 Jul 2001 01:02:50 +0000
branchNSS_3_3_BRANCH
changeset 1715 48134f1296f7b68d6110baef195eb84c736e4afc
parent 1712 399ad9228f7852b5e9ff3dbdfa6a5e6135d2dfbb
child 1720 147d220e2e85008d1ab1fe20916959f5456af6ac
push idunknown
push userunknown
push dateunknown
fixup commit for tag 'NSS_3_3_BETA2'
security/nss/lib/certdb/pcertdb.c
security/nss/lib/ckfw/object.c
security/nss/lib/nss/nss.def
security/nss/lib/pk11wrap/pk11kea.c
security/nss/lib/pkcs12/p12d.c
security/nss/lib/softoken/keydb.c
security/nss/lib/softoken/private.h
security/nss/lib/util/secerr.h
new file mode 100644
--- /dev/null
+++ b/security/nss/lib/certdb/pcertdb.c
@@ -0,0 +1,7480 @@
+/*
+ * The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ * 
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ * 
+ * The Original Code is the Netscape security libraries.
+ * 
+ * The Initial Developer of the Original Code is Netscape
+ * Communications Corporation.  Portions created by Netscape are 
+ * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
+ * Rights Reserved.
+ * 
+ * Contributor(s):
+ * 
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU General Public License Version 2 or later (the
+ * "GPL"), in which case the provisions of the GPL are applicable 
+ * instead of those above.  If you wish to allow use of your 
+ * version of this file only under the terms of the GPL and not to
+ * allow others to use your version of this file under the MPL,
+ * indicate your decision by deleting the provisions above and
+ * replace them with the notice and other provisions required by
+ * the GPL.  If you do not delete the provisions above, a recipient
+ * may use your version of this file under either the MPL or the
+ * GPL.
+ */
+
+/*
+ * Permanent Certificate database handling code 
+ *
+ * $Id$
+ */
+#include "prtime.h"
+
+#include "cert.h"
+#include "mcom_db.h"
+#include "certdb.h"
+#include "secitem.h"
+#include "secder.h"
+
+/* Call to PK11_FreeSlot below */
+
+#include "secasn1.h"
+#include "secerr.h"
+#include "nssilock.h"
+#include "prmon.h"
+#include "nsslocks.h"
+#include "base64.h"
+#include "sechash.h"
+#include "plhash.h"
+
+#include "cdbhdl.h"
+
+/* forward declaration */
+CERTCertificate *
+CERT_FindCertByDERCertNoLocking(CERTCertDBHandle *handle, SECItem *derCert);
+
+/*
+ * the following functions are wrappers for the db library that implement
+ * a global lock to make the database thread safe.
+ */
+static PZLock *dbLock = NULL;
+
+void
+certdb_InitDBLock(void)
+{
+    if (dbLock == NULL) {
+	nss_InitLock(&dbLock, nssILockCertDB);
+	PORT_Assert(dbLock != NULL);
+    }
+
+    return;
+}
+
+static int
+certdb_Get(DB *db, DBT *key, DBT *data, unsigned int flags)
+{
+    PRStatus prstat;
+    int ret;
+    
+    PORT_Assert(dbLock != NULL);
+    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;
+
+    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);
+}
+
+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);
+
+    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;
+
+    PORT_Assert(dbLock != NULL);
+    PZ_Lock(dbLock);
+
+    (* db->close)(db);
+    
+    prstat = PZ_Unlock(dbLock);
+
+    return;
+}
+
+/* forward references */
+static void CERT_DestroyCertificateNoLocking(CERTCertificate *cert);
+static SECStatus AddCertToSPKDigestTable(CERTCertDBHandle *handle,
+					 CERTCertificate *cert);
+static SECStatus RemoveCertFromSPKDigestTable(CERTCertDBHandle *handle,
+					      CERTCertificate *cert);
+
+static SECStatus
+DeleteDBEntry(CERTCertDBHandle *handle, certDBEntryType type, SECItem *dbkey)
+{
+    DBT key;
+    int ret;
+
+    /* init the database key */
+    key.data = dbkey->data;
+    key.size = dbkey->len;
+    
+    dbkey->data[0] = (unsigned char)type;
+
+    /* delete entry from database */
+    ret = certdb_Del(handle->permCertDB, &key, 0 );
+    if ( ret != 0 ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+
+    ret = certdb_Sync(handle->permCertDB, 0);
+    if ( ret ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+
+    return(SECSuccess);
+    
+loser:
+    return(SECFailure);
+}
+
+static SECStatus
+ReadDBEntry(CERTCertDBHandle *handle, certDBEntryCommon *entry,
+	    SECItem *dbkey, SECItem *dbentry, PRArenaPool *arena)
+{
+    DBT data, key;
+    int ret;
+    unsigned char *buf;
+    
+    /* init the database key */
+    key.data = dbkey->data;
+    key.size = dbkey->len;
+    
+    dbkey->data[0] = (unsigned char)entry->type;
+
+    /* read entry from database */
+    ret = certdb_Get(handle->permCertDB, &key, &data, 0 );
+    if ( ret != 0 ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+    
+    /* validate the entry */
+    if ( data.size < SEC_DB_ENTRY_HEADER_LEN ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+    buf = (unsigned char *)data.data;
+    if ( buf[0] != (unsigned char)CERT_DB_FILE_VERSION ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+    if ( buf[1] != (unsigned char)entry->type ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+
+    /* copy out header information */
+    entry->version = (unsigned int)buf[0];
+    entry->type = (certDBEntryType)buf[1];
+    entry->flags = (unsigned int)buf[2];
+    
+    /* format body of entry for return to caller */
+    dbentry->len = data.size - SEC_DB_ENTRY_HEADER_LEN;
+    if ( dbentry->len ) {
+	dbentry->data = (unsigned char *)PORT_ArenaAlloc(arena, dbentry->len);
+	if ( dbentry->data == NULL ) {
+	    PORT_SetError(SEC_ERROR_NO_MEMORY);
+	    goto loser;
+	}
+    
+	PORT_Memcpy(dbentry->data, &buf[SEC_DB_ENTRY_HEADER_LEN],
+		  dbentry->len);
+    } else {
+	dbentry->data = NULL;
+    }
+    
+    return(SECSuccess);
+
+loser:
+    return(SECFailure);
+}
+
+/**
+ ** Implement low level database access
+ **/
+static SECStatus
+WriteDBEntry(CERTCertDBHandle *handle, certDBEntryCommon *entry,
+	     SECItem *dbkey, SECItem *dbentry)
+{
+    int ret;
+    DBT data, key;
+    unsigned char *buf;
+    
+    data.data = dbentry->data;
+    data.size = dbentry->len;
+    
+    buf = (unsigned char*)data.data;
+    
+    buf[0] = (unsigned char)entry->version;
+    buf[1] = (unsigned char)entry->type;
+    buf[2] = (unsigned char)entry->flags;
+    
+    key.data = dbkey->data;
+    key.size = dbkey->len;
+    
+    dbkey->data[0] = (unsigned char)entry->type;
+
+    /* put the record into the database now */
+    ret = certdb_Put(handle->permCertDB, &key, &data, 0);
+
+    if ( ret != 0 ) {
+	goto loser;
+    }
+
+    ret = certdb_Sync( handle->permCertDB, 0 );
+    
+    if ( ret ) {
+	goto loser;
+    }
+
+    return(SECSuccess);
+
+loser:
+    return(SECFailure);
+}
+
+/*
+ * encode a database cert record
+ */
+static SECStatus
+EncodeDBCertEntry(certDBEntryCert *entry, PRArenaPool *arena, SECItem *dbitem)
+{
+    unsigned int nnlen;
+    unsigned char *buf;
+    char *nn;
+    char zbuf = 0;
+    
+    if ( entry->nickname ) {
+	nn = entry->nickname;
+    } else {
+	nn = &zbuf;
+    }
+    nnlen = PORT_Strlen(nn) + 1;
+    
+    /* allocate space for encoded database record, including space
+     * for low level header
+     */
+    dbitem->len = entry->derCert.len + nnlen + DB_CERT_ENTRY_HEADER_LEN +
+	SEC_DB_ENTRY_HEADER_LEN;
+    
+    dbitem->data = (unsigned char *)PORT_ArenaAlloc(arena, dbitem->len);
+    if ( dbitem->data == NULL) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    
+    /* fill in database record */
+    buf = &dbitem->data[SEC_DB_ENTRY_HEADER_LEN];
+    
+    buf[0] = ( entry->trust.sslFlags >> 8 ) & 0xff;
+    buf[1] = entry->trust.sslFlags & 0xff;
+    buf[2] = ( entry->trust.emailFlags >> 8 ) & 0xff;
+    buf[3] = entry->trust.emailFlags & 0xff;
+    buf[4] = ( entry->trust.objectSigningFlags >> 8 ) & 0xff;
+    buf[5] = entry->trust.objectSigningFlags & 0xff;
+    buf[6] = ( entry->derCert.len >> 8 ) & 0xff;
+    buf[7] = entry->derCert.len & 0xff;
+    buf[8] = ( nnlen >> 8 ) & 0xff;
+    buf[9] = nnlen & 0xff;
+    
+    PORT_Memcpy(&buf[DB_CERT_ENTRY_HEADER_LEN], entry->derCert.data,
+	      entry->derCert.len);
+
+    PORT_Memcpy(&buf[DB_CERT_ENTRY_HEADER_LEN + entry->derCert.len],
+	      nn, nnlen);
+
+    return(SECSuccess);
+
+loser:
+    return(SECFailure);
+}
+
+/*
+ * encode a database key for a cert record
+ */
+static SECStatus
+EncodeDBCertKey(SECItem *certKey, PRArenaPool *arena, SECItem *dbkey)
+{
+    dbkey->len = certKey->len + SEC_DB_KEY_HEADER_LEN;
+    dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey->len);
+    if ( dbkey->data == NULL ) {
+	goto loser;
+    }
+    PORT_Memcpy(&dbkey->data[SEC_DB_KEY_HEADER_LEN],
+	      certKey->data, certKey->len);
+    dbkey->data[0] = certDBEntryTypeCert;
+
+    return(SECSuccess);
+loser:
+    return(SECFailure);
+}
+
+static SECStatus
+EncodeDBGenericKey(SECItem *certKey, PRArenaPool *arena, SECItem *dbkey, 
+				certDBEntryType entryType)
+{
+    /*
+     * we only allow _one_ KRL key!
+     */
+    if (entryType == certDBEntryTypeKeyRevocation) {
+	dbkey->len = SEC_DB_KEY_HEADER_LEN;
+ 	dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey->len);
+	if ( dbkey->data == NULL ) {
+	    goto loser;
+	}
+        dbkey->data[0] = (unsigned char) entryType;
+        return(SECSuccess);
+    }
+    
+
+    dbkey->len = certKey->len + SEC_DB_KEY_HEADER_LEN;
+    dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey->len);
+    if ( dbkey->data == NULL ) {
+	goto loser;
+    }
+    PORT_Memcpy(&dbkey->data[SEC_DB_KEY_HEADER_LEN],
+	      certKey->data, certKey->len);
+    dbkey->data[0] = (unsigned char) entryType;
+
+    return(SECSuccess);
+loser:
+    return(SECFailure);
+}
+
+static SECStatus
+DecodeDBCertEntry(certDBEntryCert *entry, SECItem *dbentry)
+{
+    unsigned int nnlen;
+    int headerlen;
+    int lenoff;
+
+    /* allow updates of old versions of the database */
+    switch ( entry->common.version ) {
+      case 5:
+	headerlen = DB_CERT_V5_ENTRY_HEADER_LEN;
+	lenoff = 3;
+	break;
+      case 6:
+	/* should not get here */
+	PORT_Assert(0);
+	headerlen = DB_CERT_V6_ENTRY_HEADER_LEN;
+	lenoff = 3;
+	break;
+      case 7:
+	headerlen = DB_CERT_ENTRY_HEADER_LEN;
+	lenoff = 6;
+	break;
+      default:
+	/* better not get here */
+	PORT_Assert(0);
+	headerlen = DB_CERT_V5_ENTRY_HEADER_LEN;
+	lenoff = 3;
+	break;
+    }
+    
+    /* is record long enough for header? */
+    if ( dbentry->len < headerlen ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+    
+    /* is database entry correct length? */
+    entry->derCert.len = ( ( dbentry->data[lenoff] << 8 ) |
+			  dbentry->data[lenoff+1] );
+    nnlen = ( ( dbentry->data[lenoff+2] << 8 ) | dbentry->data[lenoff+3] );
+    if ( ( entry->derCert.len + nnlen + headerlen )
+	!= dbentry->len) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+    
+    /* copy the dercert */
+    entry->derCert.data = (unsigned char *)PORT_ArenaAlloc(entry->common.arena,
+							   entry->derCert.len);
+    if ( entry->derCert.data == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    PORT_Memcpy(entry->derCert.data, &dbentry->data[headerlen],
+	      entry->derCert.len);
+
+    /* copy the nickname */
+    if ( nnlen > 1 ) {
+	entry->nickname = (char *)PORT_ArenaAlloc(entry->common.arena, nnlen);
+	if ( entry->nickname == NULL ) {
+	    PORT_SetError(SEC_ERROR_NO_MEMORY);
+	    goto loser;
+	}
+	PORT_Memcpy(entry->nickname,
+		    &dbentry->data[headerlen +
+				   entry->derCert.len],
+		    nnlen);
+    } else {
+	entry->nickname = NULL;
+    }
+    
+    if ( entry->common.version < 7 ) {
+	/* allow updates of v5 db */
+	entry->trust.sslFlags = dbentry->data[0];
+	entry->trust.emailFlags = dbentry->data[1];
+	entry->trust.objectSigningFlags = dbentry->data[2];
+    } else {
+	entry->trust.sslFlags = ( dbentry->data[0] << 8 ) | dbentry->data[1];
+	entry->trust.emailFlags = ( dbentry->data[2] << 8 ) | dbentry->data[3];
+	entry->trust.objectSigningFlags =
+	    ( dbentry->data[4] << 8 ) | dbentry->data[5];
+    }
+    
+    return(SECSuccess);
+loser:
+    return(SECFailure);
+}
+
+
+/*
+ * Create a new certDBEntryCert from existing data
+ */
+static certDBEntryCert *
+NewDBCertEntry(SECItem *derCert, char *nickname,
+	       CERTCertTrust *trust, int flags)
+{
+    certDBEntryCert *entry;
+    PRArenaPool *arena = NULL;
+    int nnlen;
+    
+    arena = PORT_NewArena( DER_DEFAULT_CHUNKSIZE );
+
+    if ( !arena ) {
+	goto loser;
+    }
+	
+    entry = (certDBEntryCert *)PORT_ArenaZAlloc(arena, sizeof(certDBEntryCert));
+
+    if ( entry == NULL ) {
+	goto loser;
+    }
+    
+    /* fill in the dbCert */
+    entry->common.arena = arena;
+    entry->common.type = certDBEntryTypeCert;
+    entry->common.version = CERT_DB_FILE_VERSION;
+    entry->common.flags = flags;
+    
+    if ( trust ) {
+	entry->trust = *trust;
+    }
+
+    entry->derCert.data = (unsigned char *)PORT_ArenaAlloc(arena, derCert->len);
+    if ( !entry->derCert.data ) {
+	goto loser;
+    }
+    entry->derCert.len = derCert->len;
+    PORT_Memcpy(entry->derCert.data, derCert->data, derCert->len);
+    
+    nnlen = ( nickname ? strlen(nickname) + 1 : 0 );
+    
+    if ( nnlen ) {
+	entry->nickname = (char *)PORT_ArenaAlloc(arena, nnlen);
+	if ( !entry->nickname ) {
+	    goto loser;
+	}
+	PORT_Memcpy(entry->nickname, nickname, nnlen);
+	
+    } else {
+	entry->nickname = 0;
+    }
+
+    return(entry);
+
+loser:
+    
+    /* allocation error, free arena and return */
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    PORT_SetError(SEC_ERROR_NO_MEMORY);
+    return(0);
+}
+
+/*
+ * Decode a version 4 DBCert from the byte stream database format
+ * and construct a current database entry struct
+ */
+static certDBEntryCert *
+DecodeV4DBCertEntry(unsigned char *buf, int len)
+{
+    certDBEntryCert *entry;
+    int certlen;
+    int nnlen;
+    PRArenaPool *arena;
+    
+    /* make sure length is at least long enough for the header */
+    if ( len < DBCERT_V4_HEADER_LEN ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	return(0);
+    }
+
+    /* get other lengths */
+    certlen = buf[3] << 8 | buf[4];
+    nnlen = buf[5] << 8 | buf[6];
+    
+    /* make sure DB entry is the right size */
+    if ( ( certlen + nnlen + DBCERT_V4_HEADER_LEN ) != len ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	return(0);
+    }
+
+    /* allocate arena */
+    arena = PORT_NewArena( DER_DEFAULT_CHUNKSIZE );
+
+    if ( !arena ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	return(0);
+    }
+	
+    /* allocate structure and members */
+    entry = (certDBEntryCert *)  PORT_ArenaAlloc(arena, sizeof(certDBEntryCert));
+
+    if ( !entry ) {
+	goto loser;
+    }
+
+    entry->derCert.data = (unsigned char *)PORT_ArenaAlloc(arena, certlen);
+    if ( !entry->derCert.data ) {
+	goto loser;
+    }
+    entry->derCert.len = certlen;
+    
+    if ( nnlen ) {
+	entry->nickname = (char *) PORT_ArenaAlloc(arena, nnlen);
+	if ( !entry->nickname ) {
+	    goto loser;
+	}
+    } else {
+	entry->nickname = 0;
+    }
+
+    entry->common.arena = arena;
+    entry->common.version = CERT_DB_FILE_VERSION;
+    entry->common.type = certDBEntryTypeCert;
+    entry->common.flags = 0;
+    entry->trust.sslFlags = buf[0];
+    entry->trust.emailFlags = buf[1];
+    entry->trust.objectSigningFlags = buf[2];
+
+    PORT_Memcpy(entry->derCert.data, &buf[DBCERT_V4_HEADER_LEN], certlen);
+    PORT_Memcpy(entry->nickname, &buf[DBCERT_V4_HEADER_LEN + certlen], nnlen);
+
+    if (PORT_Strcmp(entry->nickname,"Server-Cert") == 0) {
+	entry->trust.sslFlags |= CERTDB_USER;
+    }
+
+    return(entry);
+    
+loser:
+    PORT_FreeArena(arena, PR_FALSE);
+    PORT_SetError(SEC_ERROR_NO_MEMORY);
+    return(0);
+}
+
+/*
+ * Encode a Certificate database entry into byte stream suitable for
+ * the database
+ */
+static SECStatus
+WriteDBCertEntry(CERTCertDBHandle *handle, certDBEntryCert *entry)
+{
+    SECItem dbitem, dbkey;
+    PRArenaPool *tmparena = NULL;
+    SECItem tmpitem;
+    SECStatus rv;
+    
+    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( tmparena == NULL ) {
+	goto loser;
+    }
+    
+    rv = EncodeDBCertEntry(entry, tmparena, &dbitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+
+    /* get the database key and format it */
+    rv = CERT_KeyFromDERCert(tmparena, &entry->derCert, &tmpitem);
+    if ( rv == SECFailure ) {
+	goto loser;
+    }
+
+    rv = EncodeDBCertKey(&tmpitem, tmparena, &dbkey);
+    if ( rv == SECFailure ) {
+	goto loser;
+    }
+    
+    /* now write it to the database */
+    rv = WriteDBEntry(handle, &entry->common, &dbkey, &dbitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    PORT_FreeArena(tmparena, PR_FALSE);
+    return(SECSuccess);
+
+loser:
+    if ( tmparena ) {
+	PORT_FreeArena(tmparena, PR_FALSE);
+    }
+    return(SECFailure);
+}
+
+
+/*
+ * delete a certificate entry
+ */
+static SECStatus
+DeleteDBCertEntry(CERTCertDBHandle *handle, SECItem *certKey)
+{
+    SECItem dbkey;
+    PRArenaPool *arena = NULL;
+    SECStatus rv;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+
+    rv = EncodeDBCertKey(certKey, arena, &dbkey);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    rv = DeleteDBEntry(handle, certDBEntryTypeCert, &dbkey);
+    if ( rv == SECFailure ) {
+	goto loser;
+    }
+
+    PORT_FreeArena(arena, PR_FALSE);
+    return(SECSuccess);
+
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(SECFailure);
+}
+
+/*
+ * Read a certificate entry
+ */
+static certDBEntryCert *
+ReadDBCertEntry(CERTCertDBHandle *handle, SECItem *certKey)
+{
+    PRArenaPool *arena = NULL;
+    PRArenaPool *tmparena = NULL;
+    certDBEntryCert *entry;
+    SECItem dbkey;
+    SECItem dbentry;
+    SECStatus rv;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( tmparena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    
+    entry = (certDBEntryCert *)PORT_ArenaAlloc(arena, sizeof(certDBEntryCert));
+    if ( entry == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    entry->common.arena = arena;
+    entry->common.type = certDBEntryTypeCert;
+
+    rv = EncodeDBCertKey(certKey, tmparena, &dbkey);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena);
+    if ( rv == SECFailure ) {
+	goto loser;
+    }
+
+    rv = DecodeDBCertEntry(entry, &dbentry);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    PORT_FreeArena(tmparena, PR_FALSE);
+    return(entry);
+    
+loser:
+    if ( tmparena ) {
+	PORT_FreeArena(tmparena, PR_FALSE);
+    }
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(NULL);
+}
+
+/*
+ * encode a database cert record
+ */
+static SECStatus
+EncodeDBCrlEntry(certDBEntryRevocation *entry, PRArenaPool *arena, SECItem *dbitem)
+{
+    unsigned int nnlen = 0;
+    unsigned char *buf;
+  
+    if (entry->url) {  
+	nnlen = PORT_Strlen(entry->url) + 1;
+    }
+    
+    /* allocate space for encoded database record, including space
+     * for low level header
+     */
+    dbitem->len = entry->derCrl.len + nnlen 
+		+ SEC_DB_ENTRY_HEADER_LEN + DB_CRL_ENTRY_HEADER_LEN;
+    
+    dbitem->data = (unsigned char *)PORT_ArenaAlloc(arena, dbitem->len);
+    if ( dbitem->data == NULL) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    
+    /* fill in database record */
+    buf = &dbitem->data[SEC_DB_ENTRY_HEADER_LEN];
+    
+    buf[0] = ( entry->derCrl.len >> 8 ) & 0xff;
+    buf[1] = entry->derCrl.len & 0xff;
+    buf[2] = ( nnlen >> 8 ) & 0xff;
+    buf[3] = nnlen & 0xff;
+    
+    PORT_Memcpy(&buf[DB_CRL_ENTRY_HEADER_LEN], entry->derCrl.data,
+	      entry->derCrl.len);
+
+    if (nnlen != 0) {
+	PORT_Memcpy(&buf[DB_CRL_ENTRY_HEADER_LEN + entry->derCrl.len],
+	      entry->url, nnlen);
+    }
+
+    return(SECSuccess);
+
+loser:
+    return(SECFailure);
+}
+
+static SECStatus
+DecodeDBCrlEntry(certDBEntryRevocation *entry, SECItem *dbentry)
+{
+    unsigned int nnlen;
+    
+    /* is record long enough for header? */
+    if ( dbentry->len < DB_CRL_ENTRY_HEADER_LEN ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+    
+    /* is database entry correct length? */
+    entry->derCrl.len = ( ( dbentry->data[0] << 8 ) | dbentry->data[1] );
+    nnlen = ( ( dbentry->data[2] << 8 ) | dbentry->data[3] );
+    if ( ( entry->derCrl.len + nnlen + DB_CRL_ENTRY_HEADER_LEN )
+	!= dbentry->len) {
+      /* CRL entry is greater than 64 K. Hack to make this continue to work */
+      if (dbentry->len >= (0xffff - DB_CRL_ENTRY_HEADER_LEN) - nnlen) {
+          entry->derCrl.len = 
+                      (dbentry->len - DB_CRL_ENTRY_HEADER_LEN) - nnlen;
+      } else {
+          PORT_SetError(SEC_ERROR_BAD_DATABASE);
+          goto loser;
+      }    
+    }
+    
+    /* copy the dercert */
+    entry->derCrl.data = (unsigned char *)PORT_ArenaAlloc(entry->common.arena,
+							 entry->derCrl.len);
+    if ( entry->derCrl.data == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    PORT_Memcpy(entry->derCrl.data, &dbentry->data[DB_CRL_ENTRY_HEADER_LEN],
+	      entry->derCrl.len);
+
+    /* copy the url */
+    entry->url = NULL;
+    if (nnlen != 0) {
+	entry->url = (char *)PORT_ArenaAlloc(entry->common.arena, nnlen);
+	if ( entry->url == NULL ) {
+	    PORT_SetError(SEC_ERROR_NO_MEMORY);
+	    goto loser;
+	}
+	PORT_Memcpy(entry->url,
+	      &dbentry->data[DB_CRL_ENTRY_HEADER_LEN + entry->derCrl.len],
+	      nnlen);
+    }
+    
+    return(SECSuccess);
+loser:
+    return(SECFailure);
+}
+
+/*
+ * Create a new certDBEntryRevocation from existing data
+ */
+static certDBEntryRevocation *
+NewDBCrlEntry(SECItem *derCrl, char * url, certDBEntryType crlType, int flags)
+{
+    certDBEntryRevocation *entry;
+    PRArenaPool *arena = NULL;
+    int nnlen;
+    
+    arena = PORT_NewArena( DER_DEFAULT_CHUNKSIZE );
+
+    if ( !arena ) {
+	goto loser;
+    }
+	
+    entry = (certDBEntryRevocation*)
+			PORT_ArenaZAlloc(arena, sizeof(certDBEntryRevocation));
+
+    if ( entry == NULL ) {
+	goto loser;
+    }
+    
+    /* fill in the dbRevolcation */
+    entry->common.arena = arena;
+    entry->common.type = crlType;
+    entry->common.version = CERT_DB_FILE_VERSION;
+    entry->common.flags = flags;
+    
+
+    entry->derCrl.data = (unsigned char *)PORT_ArenaAlloc(arena, derCrl->len);
+    if ( !entry->derCrl.data ) {
+	goto loser;
+    }
+
+    if (url) {
+	nnlen = PORT_Strlen(url) + 1;
+	entry->url  = (char *)PORT_ArenaAlloc(arena, nnlen);
+	if ( !entry->url ) {
+	    goto loser;
+	}
+	PORT_Memcpy(entry->url, url, nnlen);
+    } else {
+	entry->url = NULL;
+    }
+
+	
+    entry->derCrl.len = derCrl->len;
+    PORT_Memcpy(entry->derCrl.data, derCrl->data, derCrl->len);
+
+    return(entry);
+
+loser:
+    
+    /* allocation error, free arena and return */
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    PORT_SetError(SEC_ERROR_NO_MEMORY);
+    return(0);
+}
+
+
+static SECStatus
+WriteDBCrlEntry(CERTCertDBHandle *handle, certDBEntryRevocation *entry )
+{
+    SECItem dbkey;
+    PRArenaPool *tmparena = NULL;
+    SECItem tmpitem,encodedEntry;
+    SECStatus rv;
+    
+    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( tmparena == NULL ) {
+	goto loser;
+    }
+
+    /* get the database key and format it */
+    rv = CERT_KeyFromDERCrl(tmparena, &entry->derCrl, &tmpitem);
+    if ( rv == SECFailure ) {
+	goto loser;
+    }
+
+    rv = EncodeDBCrlEntry(entry, tmparena, &encodedEntry);
+    if ( rv == SECFailure ) {
+	goto loser;
+    }
+
+    rv = EncodeDBGenericKey(&tmpitem, tmparena, &dbkey, entry->common.type);
+    if ( rv == SECFailure ) {
+	goto loser;
+    }
+    
+    /* now write it to the database */
+    rv = WriteDBEntry(handle, &entry->common, &dbkey, &encodedEntry);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    PORT_FreeArena(tmparena, PR_FALSE);
+    return(SECSuccess);
+
+loser:
+    if ( tmparena ) {
+	PORT_FreeArena(tmparena, PR_FALSE);
+    }
+    return(SECFailure);
+}
+/*
+ * delete a crl entry
+ */
+static SECStatus
+DeleteDBCrlEntry(CERTCertDBHandle *handle, SECItem *crlKey, 
+						certDBEntryType crlType)
+{
+    SECItem dbkey;
+    PRArenaPool *arena = NULL;
+    SECStatus rv;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+
+    rv = EncodeDBGenericKey(crlKey, arena, &dbkey, crlType);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    rv = DeleteDBEntry(handle, crlType, &dbkey);
+    if ( rv == SECFailure ) {
+	goto loser;
+    }
+
+    PORT_FreeArena(arena, PR_FALSE);
+    return(SECSuccess);
+
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(SECFailure);
+}
+
+/*
+ * Read a certificate entry
+ */
+static certDBEntryRevocation *
+ReadDBCrlEntry(CERTCertDBHandle *handle, SECItem *certKey,
+						certDBEntryType crlType)
+{
+    PRArenaPool *arena = NULL;
+    PRArenaPool *tmparena = NULL;
+    certDBEntryRevocation *entry;
+    SECItem dbkey;
+    SECItem dbentry;
+    SECStatus rv;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( tmparena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    
+    entry = (certDBEntryRevocation *)
+			PORT_ArenaAlloc(arena, sizeof(certDBEntryRevocation));
+    if ( entry == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    entry->common.arena = arena;
+    entry->common.type = crlType;
+
+    rv = EncodeDBGenericKey(certKey, tmparena, &dbkey, crlType);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena);
+    if ( rv == SECFailure ) {
+	goto loser;
+    }
+
+    rv = DecodeDBCrlEntry(entry, &dbentry);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    PORT_FreeArena(tmparena, PR_FALSE);
+    return(entry);
+    
+loser:
+    if ( tmparena ) {
+	PORT_FreeArena(tmparena, PR_FALSE);
+    }
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(NULL);
+}
+
+/*
+ * destroy a database entry
+ */
+static void
+DestroyDBEntry(certDBEntry *entry)
+{
+    PRArenaPool *arena = entry->common.arena;
+
+    /* Zero out the entry struct, so that any further attempts to use it
+     * will cause an exception (e.g. null pointer reference). */
+    PORT_Memset(&entry->common, 0, sizeof entry->common);
+    PORT_FreeArena(arena, PR_FALSE);
+
+    return;
+}
+
+/*
+ * Encode a database nickname record
+ */
+static SECStatus
+EncodeDBNicknameEntry(certDBEntryNickname *entry, PRArenaPool *arena,
+		      SECItem *dbitem)
+{
+    unsigned char *buf;
+    
+    /* allocate space for encoded database record, including space
+     * for low level header
+     */
+    dbitem->len = entry->subjectName.len + DB_NICKNAME_ENTRY_HEADER_LEN +
+	SEC_DB_ENTRY_HEADER_LEN;
+    
+    dbitem->data = (unsigned char *)PORT_ArenaAlloc(arena, dbitem->len);
+    if ( dbitem->data == NULL) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    
+    /* fill in database record */
+    buf = &dbitem->data[SEC_DB_ENTRY_HEADER_LEN];
+    
+    buf[0] = ( entry->subjectName.len >> 8 ) & 0xff;
+    buf[1] = entry->subjectName.len & 0xff;
+    
+    PORT_Memcpy(&buf[DB_NICKNAME_ENTRY_HEADER_LEN], entry->subjectName.data,
+	      entry->subjectName.len);
+
+    return(SECSuccess);
+
+loser:
+    return(SECFailure);
+}
+
+/*
+ * Encode a database key for a nickname record
+ */
+static SECStatus
+EncodeDBNicknameKey(char *nickname, PRArenaPool *arena,
+		    SECItem *dbkey)
+{
+    unsigned int nnlen;
+    
+    nnlen = PORT_Strlen(nickname) + 1; /* includes null */
+
+    /* now get the database key and format it */
+    dbkey->len = nnlen + SEC_DB_KEY_HEADER_LEN;
+    dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey->len);
+    if ( dbkey->data == NULL ) {
+	goto loser;
+    }
+    PORT_Memcpy(&dbkey->data[SEC_DB_KEY_HEADER_LEN], nickname, nnlen);
+    dbkey->data[0] = certDBEntryTypeNickname;
+
+    return(SECSuccess);
+
+loser:
+    return(SECFailure);
+}
+
+static SECStatus
+DecodeDBNicknameEntry(certDBEntryNickname *entry, SECItem *dbentry,
+                      char *nickname)
+{
+    /* is record long enough for header? */
+    if ( dbentry->len < DB_NICKNAME_ENTRY_HEADER_LEN ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+    
+    /* is database entry correct length? */
+    entry->subjectName.len = ( ( dbentry->data[0] << 8 ) | dbentry->data[1] );
+    if (( entry->subjectName.len + DB_NICKNAME_ENTRY_HEADER_LEN ) !=
+	dbentry->len ){
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+    
+    /* copy the certkey */
+    entry->subjectName.data =
+	(unsigned char *)PORT_ArenaAlloc(entry->common.arena,
+					 entry->subjectName.len);
+    if ( entry->subjectName.data == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    PORT_Memcpy(entry->subjectName.data,
+	      &dbentry->data[DB_NICKNAME_ENTRY_HEADER_LEN],
+	      entry->subjectName.len);
+    
+    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)
+{
+    PRArenaPool *arena = NULL;
+    certDBEntryNickname *entry;
+    int nnlen;
+    SECStatus rv;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    entry = (certDBEntryNickname *)PORT_ArenaAlloc(arena,
+						 sizeof(certDBEntryNickname));
+    if ( entry == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    /* init common fields */
+    entry->common.arena = arena;
+    entry->common.type = certDBEntryTypeNickname;
+    entry->common.version = CERT_DB_FILE_VERSION;
+    entry->common.flags = flags;
+
+    /* copy the nickname */
+    nnlen = PORT_Strlen(nickname) + 1;
+    
+    entry->nickname = (char*)PORT_ArenaAlloc(arena, nnlen);
+    if ( entry->nickname == NULL ) {
+	goto loser;
+    }
+    
+    PORT_Memcpy(entry->nickname, nickname, nnlen);
+    
+    rv = SECITEM_CopyItem(arena, &entry->subjectName, subjectName);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    return(entry);
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(NULL);
+}
+
+/*
+ * delete a nickname entry
+ */
+static SECStatus
+DeleteDBNicknameEntry(CERTCertDBHandle *handle, char *nickname)
+{
+    PRArenaPool *arena = NULL;
+    SECStatus rv;
+    SECItem dbkey;
+    
+    if ( nickname == NULL ) {
+	return(SECSuccess);
+    }
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+
+    rv = EncodeDBNicknameKey(nickname, arena, &dbkey);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+
+    rv = DeleteDBEntry(handle, certDBEntryTypeNickname, &dbkey);
+    if ( rv == SECFailure ) {
+	goto loser;
+    }
+
+    PORT_FreeArena(arena, PR_FALSE);
+    return(SECSuccess);
+
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(SECFailure);
+}
+
+/*
+ * Read a nickname entry
+ */
+static certDBEntryNickname *
+ReadDBNicknameEntry(CERTCertDBHandle *handle, char *nickname)
+{
+    PRArenaPool *arena = NULL;
+    PRArenaPool *tmparena = NULL;
+    certDBEntryNickname *entry;
+    SECItem dbkey;
+    SECItem dbentry;
+    SECStatus rv;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( tmparena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    
+    entry = (certDBEntryNickname *)PORT_ArenaAlloc(arena,
+						 sizeof(certDBEntryNickname));
+    if ( entry == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    entry->common.arena = arena;
+    entry->common.type = certDBEntryTypeNickname;
+
+    rv = EncodeDBNicknameKey(nickname, tmparena, &dbkey);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena);
+    if ( rv == SECFailure ) {
+	goto loser;
+    }
+
+    /* is record long enough for header? */
+    if ( dbentry.len < DB_NICKNAME_ENTRY_HEADER_LEN ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+
+    rv = DecodeDBNicknameEntry(entry, &dbentry, 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(CERTCertDBHandle *handle, certDBEntryNickname *entry)
+{
+    SECItem dbitem, dbkey;
+    PRArenaPool *tmparena = NULL;
+    SECStatus rv;
+    
+    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( tmparena == NULL ) {
+	goto loser;
+    }
+    
+    rv = EncodeDBNicknameEntry(entry, tmparena, &dbitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+
+    rv = EncodeDBNicknameKey(entry->nickname, tmparena, &dbkey);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+
+    /* now write it to the database */
+    rv = WriteDBEntry(handle, &entry->common, &dbkey, &dbitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    PORT_FreeArena(tmparena, PR_FALSE);
+    return(SECSuccess);
+
+loser:
+    if ( tmparena ) {
+	PORT_FreeArena(tmparena, PR_FALSE);
+    }
+    return(SECFailure);
+    
+}
+
+/*
+ * Encode a database smime record
+ */
+static SECStatus
+EncodeDBSMimeEntry(certDBEntrySMime *entry, PRArenaPool *arena,
+		   SECItem *dbitem)
+{
+    unsigned char *buf;
+    
+    /* allocate space for encoded database record, including space
+     * for low level header
+     */
+    dbitem->len = entry->subjectName.len + entry->smimeOptions.len +
+	entry->optionsDate.len +
+	DB_SMIME_ENTRY_HEADER_LEN + SEC_DB_ENTRY_HEADER_LEN;
+    
+    dbitem->data = (unsigned char *)PORT_ArenaAlloc(arena, dbitem->len);
+    if ( dbitem->data == NULL) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    
+    /* fill in database record */
+    buf = &dbitem->data[SEC_DB_ENTRY_HEADER_LEN];
+    
+    buf[0] = ( entry->subjectName.len >> 8 ) & 0xff;
+    buf[1] = entry->subjectName.len & 0xff;
+    buf[2] = ( entry->smimeOptions.len >> 8 ) & 0xff;
+    buf[3] = entry->smimeOptions.len & 0xff;
+    buf[4] = ( entry->optionsDate.len >> 8 ) & 0xff;
+    buf[5] = entry->optionsDate.len & 0xff;
+
+    /* if no smime options, then there should not be an options date either */
+    PORT_Assert( ! ( ( entry->smimeOptions.len == 0 ) &&
+		    ( entry->optionsDate.len != 0 ) ) );
+    
+    PORT_Memcpy(&buf[DB_SMIME_ENTRY_HEADER_LEN], entry->subjectName.data,
+	      entry->subjectName.len);
+    if ( entry->smimeOptions.len ) {
+	PORT_Memcpy(&buf[DB_SMIME_ENTRY_HEADER_LEN+entry->subjectName.len],
+		    entry->smimeOptions.data,
+		    entry->smimeOptions.len);
+	PORT_Memcpy(&buf[DB_SMIME_ENTRY_HEADER_LEN + entry->subjectName.len +
+			 entry->smimeOptions.len],
+		    entry->optionsDate.data,
+		    entry->optionsDate.len);
+    }
+
+    return(SECSuccess);
+
+loser:
+    return(SECFailure);
+}
+
+/*
+ * Encode a database key for a SMIME record
+ */
+static SECStatus
+EncodeDBSMimeKey(char *emailAddr, PRArenaPool *arena,
+		 SECItem *dbkey)
+{
+    unsigned int addrlen;
+    
+    addrlen = PORT_Strlen(emailAddr) + 1; /* includes null */
+
+    /* now get the database key and format it */
+    dbkey->len = addrlen + SEC_DB_KEY_HEADER_LEN;
+    dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey->len);
+    if ( dbkey->data == NULL ) {
+	goto loser;
+    }
+    PORT_Memcpy(&dbkey->data[SEC_DB_KEY_HEADER_LEN], emailAddr, addrlen);
+    dbkey->data[0] = certDBEntryTypeSMimeProfile;
+
+    return(SECSuccess);
+
+loser:
+    return(SECFailure);
+}
+
+/*
+ * Decode a database SMIME record
+ */
+static SECStatus
+DecodeDBSMimeEntry(certDBEntrySMime *entry, SECItem *dbentry, char *emailAddr)
+{
+    /* is record long enough for header? */
+    if ( dbentry->len < DB_SMIME_ENTRY_HEADER_LEN ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+    
+    /* is database entry correct length? */
+    entry->subjectName.len = ( ( dbentry->data[0] << 8 ) | dbentry->data[1] );
+    entry->smimeOptions.len = ( ( dbentry->data[2] << 8 ) | dbentry->data[3] );
+    entry->optionsDate.len = ( ( dbentry->data[4] << 8 ) | dbentry->data[5] );
+    if (( entry->subjectName.len + entry->smimeOptions.len +
+	 entry->optionsDate.len + DB_SMIME_ENTRY_HEADER_LEN ) != dbentry->len){
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+    
+    /* copy the subject name */
+    entry->subjectName.data =
+	(unsigned char *)PORT_ArenaAlloc(entry->common.arena,
+					 entry->subjectName.len);
+    if ( entry->subjectName.data == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    PORT_Memcpy(entry->subjectName.data,
+	      &dbentry->data[DB_SMIME_ENTRY_HEADER_LEN],
+	      entry->subjectName.len);
+
+    /* copy the smime options */
+    if ( entry->smimeOptions.len ) {
+	entry->smimeOptions.data =
+	    (unsigned char *)PORT_ArenaAlloc(entry->common.arena,
+					     entry->smimeOptions.len);
+	if ( entry->smimeOptions.data == NULL ) {
+	    PORT_SetError(SEC_ERROR_NO_MEMORY);
+	    goto loser;
+	}
+	PORT_Memcpy(entry->smimeOptions.data,
+		    &dbentry->data[DB_SMIME_ENTRY_HEADER_LEN +
+				   entry->subjectName.len],
+		    entry->smimeOptions.len);
+    }
+    if ( entry->optionsDate.len ) {
+	entry->optionsDate.data =
+	    (unsigned char *)PORT_ArenaAlloc(entry->common.arena,
+					     entry->optionsDate.len);
+	if ( entry->optionsDate.data == NULL ) {
+	    PORT_SetError(SEC_ERROR_NO_MEMORY);
+	    goto loser;
+	}
+	PORT_Memcpy(entry->optionsDate.data,
+		    &dbentry->data[DB_SMIME_ENTRY_HEADER_LEN +
+				   entry->subjectName.len +
+				   entry->smimeOptions.len],
+		    entry->optionsDate.len);
+    }
+
+    /* both options and options date must either exist or not exist */
+    if ( ( ( entry->optionsDate.len == 0 ) ||
+	  ( entry->smimeOptions.len == 0 ) ) &&
+	entry->smimeOptions.len != entry->optionsDate.len ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+
+    entry->emailAddr = (char *)PORT_Alloc(PORT_Strlen(emailAddr)+1);
+    if ( entry->emailAddr ) {
+	PORT_Strcpy(entry->emailAddr, emailAddr);
+    }
+    
+    return(SECSuccess);
+
+loser:
+    return(SECFailure);
+}
+
+/*
+ * create a new SMIME entry
+ */
+static certDBEntrySMime *
+NewDBSMimeEntry(char *emailAddr, SECItem *subjectName, SECItem *smimeOptions,
+		SECItem *optionsDate, unsigned int flags)
+{
+    PRArenaPool *arena = NULL;
+    certDBEntrySMime *entry;
+    int addrlen;
+    SECStatus rv;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    entry = (certDBEntrySMime *)PORT_ArenaAlloc(arena,
+						sizeof(certDBEntrySMime));
+    if ( entry == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    /* init common fields */
+    entry->common.arena = arena;
+    entry->common.type = certDBEntryTypeSMimeProfile;
+    entry->common.version = CERT_DB_FILE_VERSION;
+    entry->common.flags = flags;
+
+    /* copy the email addr */
+    addrlen = PORT_Strlen(emailAddr) + 1;
+    
+    entry->emailAddr = (char*)PORT_ArenaAlloc(arena, addrlen);
+    if ( entry->emailAddr == NULL ) {
+	goto loser;
+    }
+    
+    PORT_Memcpy(entry->emailAddr, emailAddr, addrlen);
+    
+    /* copy the subject name */
+    rv = SECITEM_CopyItem(arena, &entry->subjectName, subjectName);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+
+    /* copy the smime options */
+    if ( smimeOptions ) {
+	rv = SECITEM_CopyItem(arena, &entry->smimeOptions, smimeOptions);
+	if ( rv != SECSuccess ) {
+	    goto loser;
+	}
+    } else {
+	PORT_Assert(optionsDate == NULL);
+	entry->smimeOptions.data = NULL;
+	entry->smimeOptions.len = 0;
+    }
+
+    /* copy the options date */
+    if ( optionsDate ) {
+	rv = SECITEM_CopyItem(arena, &entry->optionsDate, optionsDate);
+	if ( rv != SECSuccess ) {
+	    goto loser;
+	}
+    } else {
+	PORT_Assert(smimeOptions == NULL);
+	entry->optionsDate.data = NULL;
+	entry->optionsDate.len = 0;
+    }
+    
+    return(entry);
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(NULL);
+}
+
+/*
+ * delete a SMIME entry
+ */
+static SECStatus
+DeleteDBSMimeEntry(CERTCertDBHandle *handle, char *emailAddr)
+{
+    PRArenaPool *arena = NULL;
+    SECStatus rv;
+    SECItem dbkey;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+
+    rv = EncodeDBSMimeKey(emailAddr, arena, &dbkey);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+
+    rv = DeleteDBEntry(handle, certDBEntryTypeSMimeProfile, &dbkey);
+    if ( rv == SECFailure ) {
+	goto loser;
+    }
+
+    PORT_FreeArena(arena, PR_FALSE);
+    return(SECSuccess);
+
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(SECFailure);
+}
+
+/*
+ * Read a SMIME entry
+ */
+static certDBEntrySMime *
+ReadDBSMimeEntry(CERTCertDBHandle *handle, char *emailAddr)
+{
+    PRArenaPool *arena = NULL;
+    PRArenaPool *tmparena = NULL;
+    certDBEntrySMime *entry;
+    SECItem dbkey;
+    SECItem dbentry;
+    SECStatus rv;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( tmparena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    
+    entry = (certDBEntrySMime *)PORT_ArenaAlloc(arena,
+						sizeof(certDBEntrySMime));
+    if ( entry == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    entry->common.arena = arena;
+    entry->common.type = certDBEntryTypeSMimeProfile;
+
+    rv = EncodeDBSMimeKey(emailAddr, tmparena, &dbkey);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena);
+    if ( rv == SECFailure ) {
+	goto loser;
+    }
+
+    /* is record long enough for header? */
+    if ( dbentry.len < DB_SMIME_ENTRY_HEADER_LEN ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+
+    rv = DecodeDBSMimeEntry(entry, &dbentry, emailAddr);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    PORT_FreeArena(tmparena, PR_FALSE);
+    return(entry);
+    
+loser:
+    if ( tmparena ) {
+	PORT_FreeArena(tmparena, PR_FALSE);
+    }
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(NULL);
+}
+
+/*
+ * Encode a SMIME entry into byte stream suitable for
+ * the database
+ */
+static SECStatus
+WriteDBSMimeEntry(CERTCertDBHandle *handle, certDBEntrySMime *entry)
+{
+    SECItem dbitem, dbkey;
+    PRArenaPool *tmparena = NULL;
+    SECStatus rv;
+    
+    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( tmparena == NULL ) {
+	goto loser;
+    }
+    
+    rv = EncodeDBSMimeEntry(entry, tmparena, &dbitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+
+    rv = EncodeDBSMimeKey(entry->emailAddr, tmparena, &dbkey);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+
+    /* now write it to the database */
+    rv = WriteDBEntry(handle, &entry->common, &dbkey, &dbitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    PORT_FreeArena(tmparena, PR_FALSE);
+    return(SECSuccess);
+
+loser:
+    if ( tmparena ) {
+	PORT_FreeArena(tmparena, PR_FALSE);
+    }
+    return(SECFailure);
+    
+}
+
+/*
+ * Encode a database subject record
+ */
+static SECStatus
+EncodeDBSubjectEntry(certDBEntrySubject *entry, PRArenaPool *arena,
+		     SECItem *dbitem)
+{
+    unsigned char *buf;
+    int len;
+    unsigned int ncerts;
+    unsigned int i;
+    unsigned char *tmpbuf;
+    unsigned int nnlen = 0;
+    unsigned int eaddrlen = 0;
+    int keyidoff;
+    SECItem *certKeys;
+    SECItem *keyIDs;
+    
+    if ( entry->nickname ) {
+	nnlen = PORT_Strlen(entry->nickname) + 1;
+    }
+    if ( entry->emailAddr ) {
+	eaddrlen = PORT_Strlen(entry->emailAddr) + 1;
+    }
+    
+    ncerts = entry->ncerts;
+    
+    /* compute the length of the entry */
+    keyidoff = DB_SUBJECT_ENTRY_HEADER_LEN + nnlen + eaddrlen;
+    len = keyidoff + 4 * ncerts;
+    for ( i = 0; i < ncerts; i++ ) {
+	len += entry->certKeys[i].len;
+	len += entry->keyIDs[i].len;
+    }
+    
+    /* allocate space for encoded database record, including space
+     * for low level header
+     */
+    dbitem->len = len + SEC_DB_ENTRY_HEADER_LEN;
+    
+    dbitem->data = (unsigned char *)PORT_ArenaAlloc(arena, dbitem->len);
+    if ( dbitem->data == NULL) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    
+    /* fill in database record */
+    buf = &dbitem->data[SEC_DB_ENTRY_HEADER_LEN];
+    
+    buf[0] = ( ncerts >> 8 ) & 0xff;
+    buf[1] = ncerts & 0xff;
+    buf[2] = ( nnlen >> 8 ) & 0xff;
+    buf[3] = nnlen & 0xff;
+    buf[4] = ( eaddrlen >> 8 ) & 0xff;
+    buf[5] = eaddrlen & 0xff;
+
+    PORT_Memcpy(&buf[DB_SUBJECT_ENTRY_HEADER_LEN], entry->nickname, nnlen);
+    PORT_Memcpy(&buf[DB_SUBJECT_ENTRY_HEADER_LEN+nnlen], entry->emailAddr,
+		eaddrlen);
+    
+    for ( i = 0; i < ncerts; i++ ) {
+
+	certKeys = entry->certKeys;
+	keyIDs = entry->keyIDs;
+
+	buf[keyidoff+i*2] = ( certKeys[i].len >> 8 ) & 0xff;
+	buf[keyidoff+1+i*2] = certKeys[i].len & 0xff;
+	buf[keyidoff+ncerts*2+i*2] = ( keyIDs[i].len >> 8 ) & 0xff;
+	buf[keyidoff+1+ncerts*2+i*2] = keyIDs[i].len & 0xff;
+    }
+    
+    /* temp pointer used to stuff certkeys and keyids into the buffer */
+    tmpbuf = &buf[keyidoff+ncerts*4];
+
+    for ( i = 0; i < ncerts; i++ ) {
+	certKeys = entry->certKeys;
+	PORT_Memcpy(tmpbuf, certKeys[i].data, certKeys[i].len);
+	tmpbuf = tmpbuf + certKeys[i].len;
+    }
+    
+    for ( i = 0; i < ncerts; i++ ) {
+	keyIDs = entry->keyIDs;
+	PORT_Memcpy(tmpbuf, keyIDs[i].data, keyIDs[i].len);
+	tmpbuf = tmpbuf + keyIDs[i].len;
+    }
+
+    PORT_Assert(tmpbuf == &buf[len]);
+    
+    return(SECSuccess);
+
+loser:
+    return(SECFailure);
+}
+
+/*
+ * Encode a database key for a subject record
+ */
+static SECStatus
+EncodeDBSubjectKey(SECItem *derSubject, PRArenaPool *arena,
+		   SECItem *dbkey)
+{
+    dbkey->len = derSubject->len + SEC_DB_KEY_HEADER_LEN;
+    dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey->len);
+    if ( dbkey->data == NULL ) {
+	goto loser;
+    }
+    PORT_Memcpy(&dbkey->data[SEC_DB_KEY_HEADER_LEN], derSubject->data,
+	      derSubject->len);
+    dbkey->data[0] = certDBEntryTypeSubject;
+
+    return(SECSuccess);
+
+loser:
+    return(SECFailure);
+}
+
+static SECStatus
+DecodeDBSubjectEntry(certDBEntrySubject *entry, SECItem *dbentry,
+		     SECItem *derSubject)
+{
+    unsigned int ncerts;
+    PRArenaPool *arena;
+    unsigned int len, itemlen;
+    unsigned char *tmpbuf;
+    unsigned int i;
+    SECStatus rv;
+    unsigned int keyidoff;
+    unsigned int nnlen, eaddrlen;
+    
+    arena = entry->common.arena;
+
+    rv = SECITEM_CopyItem(arena, &entry->derSubject, derSubject);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+
+    /* is record long enough for header? */
+    if ( dbentry->len < DB_SUBJECT_ENTRY_HEADER_LEN ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+    
+    entry->ncerts = ncerts = ( ( dbentry->data[0] << 8 ) | dbentry->data[1] );
+    nnlen = ( ( dbentry->data[2] << 8 ) | dbentry->data[3] );
+    eaddrlen = ( ( dbentry->data[4] << 8 ) | dbentry->data[5] );
+    if ( dbentry->len < ( ncerts * 4 + DB_SUBJECT_ENTRY_HEADER_LEN +
+			 nnlen + eaddrlen) ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+    
+    entry->certKeys = (SECItem *)PORT_ArenaAlloc(arena,
+						 sizeof(SECItem) * ncerts);
+    entry->keyIDs = (SECItem *)PORT_ArenaAlloc(arena,
+					       sizeof(SECItem) * ncerts);
+
+    if ( ( entry->certKeys == NULL ) || ( entry->keyIDs == NULL ) ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    if ( nnlen > 1 ) { /* null terminator is stored */
+	entry->nickname = (char *)PORT_ArenaAlloc(arena, nnlen);
+	if ( entry->nickname == NULL ) {
+	    PORT_SetError(SEC_ERROR_NO_MEMORY);
+	    goto loser;
+	}
+	PORT_Memcpy(entry->nickname,
+		    &dbentry->data[DB_SUBJECT_ENTRY_HEADER_LEN],
+		    nnlen);
+    } else {
+	entry->nickname = NULL;
+    }
+    
+    if ( eaddrlen > 1 ) { /* null terminator is stored */
+	entry->emailAddr = (char *)PORT_ArenaAlloc(arena, eaddrlen);
+	if ( entry->emailAddr == NULL ) {
+	    PORT_SetError(SEC_ERROR_NO_MEMORY);
+	    goto loser;
+	}
+	PORT_Memcpy(entry->emailAddr,
+		    &dbentry->data[DB_SUBJECT_ENTRY_HEADER_LEN+nnlen],
+		    eaddrlen);
+    } else {
+	entry->emailAddr = NULL;
+    }
+    
+    /* collect the lengths of the certKeys and keyIDs, and total the
+     * overall length.
+     */
+    keyidoff = DB_SUBJECT_ENTRY_HEADER_LEN + nnlen + eaddrlen;
+    len = keyidoff + 4 * ncerts;
+
+    tmpbuf = &dbentry->data[0];
+    
+    for ( i = 0; i < ncerts; i++ ) {
+
+	itemlen = ( tmpbuf[keyidoff + 2*i] << 8 ) | tmpbuf[keyidoff + 1 + 2*i] ;
+	len += itemlen;
+	entry->certKeys[i].len = itemlen;
+
+	itemlen = ( tmpbuf[keyidoff + 2*ncerts + 2*i] << 8 ) |
+	    tmpbuf[keyidoff + 1 + 2*ncerts + 2*i] ;
+	len += itemlen;
+	entry->keyIDs[i].len = itemlen;
+    }
+    
+    /* is database entry correct length? */
+    if ( len != dbentry->len ){
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+    
+    tmpbuf = &tmpbuf[keyidoff + 4*ncerts];
+    for ( i = 0; i < ncerts; i++ ) {
+	entry->certKeys[i].data =
+	    (unsigned char *)PORT_ArenaAlloc(arena, entry->certKeys[i].len);
+	if ( entry->certKeys[i].data == NULL ) {
+	    PORT_SetError(SEC_ERROR_NO_MEMORY);
+	    goto loser;
+	}
+	PORT_Memcpy(entry->certKeys[i].data, tmpbuf, entry->certKeys[i].len);
+	tmpbuf = &tmpbuf[entry->certKeys[i].len];
+    }
+
+    for ( i = 0; i < ncerts; i++ ) {
+	entry->keyIDs[i].data =
+	    (unsigned char *)PORT_ArenaAlloc(arena, entry->keyIDs[i].len);
+	if ( entry->keyIDs[i].data == NULL ) {
+	    PORT_SetError(SEC_ERROR_NO_MEMORY);
+	    goto loser;
+	}
+	PORT_Memcpy(entry->keyIDs[i].data, tmpbuf, entry->keyIDs[i].len);
+	tmpbuf = &tmpbuf[entry->keyIDs[i].len];
+    }
+    
+    PORT_Assert(tmpbuf == &dbentry->data[dbentry->len]);
+    
+    return(SECSuccess);
+
+loser:
+    return(SECFailure);
+}
+
+/*
+ * create a new subject entry with a single cert
+ */
+static certDBEntrySubject *
+NewDBSubjectEntry(SECItem *derSubject, SECItem *certKey,
+		  SECItem *keyID, char *nickname, char *emailAddr,
+		  unsigned int flags)
+{
+    PRArenaPool *arena = NULL;
+    certDBEntrySubject *entry;
+    SECStatus rv;
+    unsigned int nnlen;
+    unsigned int eaddrlen;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    entry = (certDBEntrySubject *)PORT_ArenaAlloc(arena,
+						  sizeof(certDBEntrySubject));
+    if ( entry == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    /* init common fields */
+    entry->common.arena = arena;
+    entry->common.type = certDBEntryTypeSubject;
+    entry->common.version = CERT_DB_FILE_VERSION;
+    entry->common.flags = flags;
+
+    /* copy the subject */
+    rv = SECITEM_CopyItem(arena, &entry->derSubject, derSubject);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    entry->ncerts = 1;
+    /* copy nickname */
+    if ( nickname && ( *nickname != '\0' ) ) {
+	nnlen = PORT_Strlen(nickname) + 1;
+	entry->nickname = (char *)PORT_ArenaAlloc(arena, nnlen);
+	if ( entry->nickname == NULL ) {
+	    goto loser;
+	}
+						  
+	PORT_Memcpy(entry->nickname, nickname, nnlen);
+    } else {
+	entry->nickname = NULL;
+    }
+    
+    /* copy email addr */
+    if ( emailAddr && ( *emailAddr != '\0' ) ) {
+	emailAddr = CERT_FixupEmailAddr(emailAddr);
+	if ( emailAddr == NULL ) {
+	    entry->emailAddr = NULL;
+	    goto loser;
+	}
+	
+	eaddrlen = PORT_Strlen(emailAddr) + 1;
+	entry->emailAddr = (char *)PORT_ArenaAlloc(arena, eaddrlen);
+	if ( entry->emailAddr == NULL ) {
+	    PORT_Free(emailAddr);
+	    goto loser;
+	}
+	
+	PORT_Memcpy(entry->emailAddr, emailAddr, eaddrlen);
+	PORT_Free(emailAddr);
+    } else {
+	entry->emailAddr = NULL;
+    }
+    
+    /* allocate space for certKeys and keyIDs */
+    entry->certKeys = (SECItem *)PORT_ArenaAlloc(arena, sizeof(SECItem));
+    entry->keyIDs = (SECItem *)PORT_ArenaAlloc(arena, sizeof(SECItem));
+    if ( ( entry->certKeys == NULL ) || ( entry->keyIDs == NULL ) ) {
+	goto loser;
+    }
+
+    /* copy the certKey and keyID */
+    rv = SECITEM_CopyItem(arena, &entry->certKeys[0], certKey);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    rv = SECITEM_CopyItem(arena, &entry->keyIDs[0], keyID);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    return(entry);
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(NULL);
+}
+
+/*
+ * delete a subject entry
+ */
+static SECStatus
+DeleteDBSubjectEntry(CERTCertDBHandle *handle, SECItem *derSubject)
+{
+    SECItem dbkey;
+    PRArenaPool *arena = NULL;
+    SECStatus rv;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+    
+    rv = EncodeDBSubjectKey(derSubject, arena, &dbkey);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    rv = DeleteDBEntry(handle, certDBEntryTypeSubject, &dbkey);
+    if ( rv == SECFailure ) {
+	goto loser;
+    }
+
+    PORT_FreeArena(arena, PR_FALSE);
+    return(SECSuccess);
+
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(SECFailure);
+}
+
+/*
+ * Read the subject entry
+ */
+static certDBEntrySubject *
+ReadDBSubjectEntry(CERTCertDBHandle *handle, SECItem *derSubject)
+{
+    PRArenaPool *arena = NULL;
+    PRArenaPool *tmparena = NULL;
+    certDBEntrySubject *entry;
+    SECItem dbkey;
+    SECItem dbentry;
+    SECStatus rv;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( tmparena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    
+    entry = (certDBEntrySubject *)PORT_ArenaAlloc(arena,
+						sizeof(certDBEntrySubject));
+    if ( entry == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    entry->common.arena = arena;
+    entry->common.type = certDBEntryTypeSubject;
+
+    rv = EncodeDBSubjectKey(derSubject, tmparena, &dbkey);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena);
+    if ( rv == SECFailure ) {
+	goto loser;
+    }
+
+    rv = DecodeDBSubjectEntry(entry, &dbentry, derSubject);
+    if ( rv == SECFailure ) {
+	goto loser;
+    }
+    
+    PORT_FreeArena(tmparena, PR_FALSE);
+    return(entry);
+    
+loser:
+    if ( tmparena ) {
+	PORT_FreeArena(tmparena, PR_FALSE);
+    }
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(NULL);
+}
+
+/*
+ * Encode a subject name entry into byte stream suitable for
+ * the database
+ */
+static SECStatus
+WriteDBSubjectEntry(CERTCertDBHandle *handle, certDBEntrySubject *entry)
+{
+    SECItem dbitem, dbkey;
+    PRArenaPool *tmparena = NULL;
+    SECStatus rv;
+    
+    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( tmparena == NULL ) {
+	goto loser;
+    }
+    
+    rv = EncodeDBSubjectEntry(entry, tmparena, &dbitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    rv = EncodeDBSubjectKey(&entry->derSubject, tmparena, &dbkey);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+
+    /* now write it to the database */
+    rv = WriteDBEntry(handle, &entry->common, &dbkey, &dbitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    PORT_FreeArena(tmparena, PR_FALSE);
+    return(SECSuccess);
+
+loser:
+    if ( tmparena ) {
+	PORT_FreeArena(tmparena, PR_FALSE);
+    }
+    return(SECFailure);
+    
+}
+
+static SECStatus
+UpdateSubjectWithEmailAddr(CERTCertificate *cert, char *emailAddr)
+{
+    CERTSubjectList *subjectList;
+    PRBool save = PR_FALSE, delold = PR_FALSE;
+    certDBEntrySubject *entry;
+    SECStatus rv;
+    
+    emailAddr = CERT_FixupEmailAddr(emailAddr);
+    if ( emailAddr == NULL ) {
+	return(SECFailure);
+    }
+    
+    subjectList = cert->subjectList;
+    PORT_Assert(subjectList != NULL);
+    
+    if ( subjectList->emailAddr ) {
+	if ( PORT_Strcmp(subjectList->emailAddr, emailAddr) != 0 ) {
+	    save = PR_TRUE;
+	    delold = PR_TRUE;
+	}
+    } else {
+	save = PR_TRUE;
+    }
+
+    if ( delold ) {
+	/* delete the old smime entry, because this cert now has a new
+	 * smime entry pointing to it
+	 */
+	PORT_Assert(save);
+	PORT_Assert(subjectList->emailAddr != NULL);
+	DeleteDBSMimeEntry(cert->dbhandle, subjectList->emailAddr);
+    }
+
+    if ( save ) {
+	unsigned int len;
+	
+	entry = subjectList->entry;
+
+	PORT_Assert(entry != NULL);
+	len = PORT_Strlen(emailAddr) + 1;
+	entry->emailAddr = (char *)PORT_ArenaAlloc(entry->common.arena, len);
+	if ( entry->emailAddr == NULL ) {
+	    goto loser;
+	}
+	PORT_Memcpy(entry->emailAddr, emailAddr, len);
+	
+	/* delete the subject entry */
+	DeleteDBSubjectEntry(cert->dbhandle, &cert->derSubject);
+
+	/* write the new one */
+	rv = WriteDBSubjectEntry(cert->dbhandle, entry);
+	if ( rv != SECSuccess ) {
+	    goto loser;
+	}
+    }
+
+    PORT_Free(emailAddr);
+    return(SECSuccess);
+
+loser:
+    PORT_Free(emailAddr);
+    return(SECFailure);
+}
+
+/*
+ * writes a nickname to an existing subject entry that does not currently
+ * have one
+ */
+static SECStatus
+AddNicknameToSubject(CERTCertificate *cert, char *nickname)
+{
+    CERTSubjectList *subjectList;
+    certDBEntrySubject *entry;
+    SECStatus rv;
+    
+    if ( nickname == NULL ) {
+	return(SECFailure);
+    }
+    
+    subjectList = cert->subjectList;
+    PORT_Assert(subjectList != NULL);
+    if ( subjectList == NULL ) {
+	goto loser;
+    }
+    
+    entry = subjectList->entry;
+    PORT_Assert(entry != NULL);
+    if ( entry == NULL ) {
+	goto loser;
+    }
+    
+    PORT_Assert(entry->nickname == NULL);
+    if ( entry->nickname != NULL ) {
+	goto loser;
+    }
+    
+    entry->nickname = (nickname) ? PORT_ArenaStrdup(entry->common.arena, nickname) : NULL;
+    
+    if ( entry->nickname == NULL ) {
+	goto loser;
+    }
+	
+    /* delete the subject entry */
+    DeleteDBSubjectEntry(cert->dbhandle, &cert->derSubject);
+
+    /* write the new one */
+    rv = WriteDBSubjectEntry(cert->dbhandle, entry);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+
+    return(SECSuccess);
+
+loser:
+    return(SECFailure);
+}
+
+/*
+ * create a new version entry
+ */
+static certDBEntryVersion *
+NewDBVersionEntry(unsigned int flags)
+{
+    PRArenaPool *arena = NULL;
+    certDBEntryVersion *entry;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    entry = (certDBEntryVersion *)PORT_ArenaAlloc(arena,
+					       sizeof(certDBEntryVersion));
+    if ( entry == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    entry->common.arena = arena;
+    entry->common.type = certDBEntryTypeVersion;
+    entry->common.version = CERT_DB_FILE_VERSION;
+    entry->common.flags = flags;
+
+    return(entry);
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(NULL);
+}
+
+/*
+ * Read the version entry
+ */
+static certDBEntryVersion *
+ReadDBVersionEntry(CERTCertDBHandle *handle)
+{
+    PRArenaPool *arena = NULL;
+    PRArenaPool *tmparena = NULL;
+    certDBEntryVersion *entry;
+    SECItem dbkey;
+    SECItem dbentry;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( tmparena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    
+    entry = (certDBEntryVersion *)PORT_ArenaAlloc(arena,
+						sizeof(certDBEntryVersion));
+    if ( entry == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    entry->common.arena = arena;
+    entry->common.type = certDBEntryTypeVersion;
+
+    /* now get the database key and format it */
+    dbkey.len = SEC_DB_VERSION_KEY_LEN + SEC_DB_KEY_HEADER_LEN;
+    dbkey.data = (unsigned char *)PORT_ArenaAlloc(tmparena, dbkey.len);
+    if ( dbkey.data == NULL ) {
+	goto loser;
+    }
+    PORT_Memcpy(&dbkey.data[SEC_DB_KEY_HEADER_LEN], SEC_DB_VERSION_KEY,
+	      SEC_DB_VERSION_KEY_LEN);
+
+    ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena);
+
+    PORT_FreeArena(tmparena, PR_FALSE);
+    return(entry);
+    
+loser:
+    if ( tmparena ) {
+	PORT_FreeArena(tmparena, PR_FALSE);
+    }
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(NULL);
+}
+
+
+/*
+ * Encode a version entry into byte stream suitable for
+ * the database
+ */
+static SECStatus
+WriteDBVersionEntry(CERTCertDBHandle *handle, certDBEntryVersion *entry)
+{
+    SECItem dbitem, dbkey;
+    PRArenaPool *tmparena = NULL;
+    SECStatus rv;
+    
+    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( tmparena == NULL ) {
+	goto loser;
+    }
+    
+    /* allocate space for encoded database record, including space
+     * for low level header
+     */
+    dbitem.len = SEC_DB_ENTRY_HEADER_LEN;
+    
+    dbitem.data = (unsigned char *)PORT_ArenaAlloc(tmparena, dbitem.len);
+    if ( dbitem.data == NULL) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    
+    /* now get the database key and format it */
+    dbkey.len = SEC_DB_VERSION_KEY_LEN + SEC_DB_KEY_HEADER_LEN;
+    dbkey.data = (unsigned char *)PORT_ArenaAlloc(tmparena, dbkey.len);
+    if ( dbkey.data == NULL ) {
+	goto loser;
+    }
+    PORT_Memcpy(&dbkey.data[SEC_DB_KEY_HEADER_LEN], SEC_DB_VERSION_KEY,
+	      SEC_DB_VERSION_KEY_LEN);
+
+    /* now write it to the database */
+    rv = WriteDBEntry(handle, &entry->common, &dbkey, &dbitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    PORT_FreeArena(tmparena, PR_FALSE);
+    return(SECSuccess);
+
+loser:
+    if ( tmparena ) {
+	PORT_FreeArena(tmparena, PR_FALSE);
+    }
+    return(SECFailure);
+}
+
+/*
+ * create a new version entry
+ */
+static certDBEntryContentVersion *
+NewDBContentVersionEntry(unsigned int flags)
+{
+    PRArenaPool *arena = NULL;
+    certDBEntryContentVersion *entry;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    entry = (certDBEntryContentVersion *)
+	PORT_ArenaAlloc(arena, sizeof(certDBEntryContentVersion));
+    if ( entry == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    entry->common.arena = arena;
+    entry->common.type = certDBEntryTypeContentVersion;
+    entry->common.version = CERT_DB_FILE_VERSION;
+    entry->common.flags = flags;
+
+    entry->contentVersion = CERT_DB_CONTENT_VERSION;
+    
+    return(entry);
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(NULL);
+}
+
+/*
+ * Read the version entry
+ */
+static certDBEntryContentVersion *
+ReadDBContentVersionEntry(CERTCertDBHandle *handle)
+{
+    PRArenaPool *arena = NULL;
+    PRArenaPool *tmparena = NULL;
+    certDBEntryContentVersion *entry;
+    SECItem dbkey;
+    SECItem dbentry;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( tmparena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    
+    entry = (certDBEntryContentVersion *)
+	PORT_ArenaAlloc(arena, sizeof(certDBEntryContentVersion));
+    if ( entry == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    entry->common.arena = arena;
+    entry->common.type = certDBEntryTypeContentVersion;
+
+    /* now get the database key and format it */
+    dbkey.len = SEC_DB_CONTENT_VERSION_KEY_LEN + SEC_DB_KEY_HEADER_LEN;
+    dbkey.data = (unsigned char *)PORT_ArenaAlloc(tmparena, dbkey.len);
+    if ( dbkey.data == NULL ) {
+	goto loser;
+    }
+    PORT_Memcpy(&dbkey.data[SEC_DB_KEY_HEADER_LEN], SEC_DB_CONTENT_VERSION_KEY,
+		SEC_DB_CONTENT_VERSION_KEY_LEN);
+
+    dbentry.len = 0;
+    dbentry.data = NULL;
+    
+    ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena);
+
+    if ( dbentry.len != 1 ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+
+    entry->contentVersion = dbentry.data[0];
+    
+    PORT_FreeArena(tmparena, PR_FALSE);
+    return(entry);
+    
+loser:
+    if ( tmparena ) {
+	PORT_FreeArena(tmparena, PR_FALSE);
+    }
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(NULL);
+}
+
+/*
+ * Encode a version entry into byte stream suitable for
+ * the database
+ */
+static SECStatus
+WriteDBContentVersionEntry(CERTCertDBHandle *handle,
+			   certDBEntryContentVersion *entry)
+{
+    SECItem dbitem, dbkey;
+    PRArenaPool *tmparena = NULL;
+    SECStatus rv;
+    
+    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( tmparena == NULL ) {
+	goto loser;
+    }
+    
+    /* allocate space for encoded database record, including space
+     * for low level header
+     */
+    dbitem.len = SEC_DB_ENTRY_HEADER_LEN + 1;
+    
+    dbitem.data = (unsigned char *)PORT_ArenaAlloc(tmparena, dbitem.len);
+    if ( dbitem.data == NULL) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    
+    dbitem.data[SEC_DB_ENTRY_HEADER_LEN] = entry->contentVersion;
+    
+    /* now get the database key and format it */
+    dbkey.len = SEC_DB_CONTENT_VERSION_KEY_LEN + SEC_DB_KEY_HEADER_LEN;
+    dbkey.data = (unsigned char *)PORT_ArenaAlloc(tmparena, dbkey.len);
+    if ( dbkey.data == NULL ) {
+	goto loser;
+    }
+    PORT_Memcpy(&dbkey.data[SEC_DB_KEY_HEADER_LEN], SEC_DB_CONTENT_VERSION_KEY,
+		SEC_DB_CONTENT_VERSION_KEY_LEN);
+
+    /* now write it to the database */
+    rv = WriteDBEntry(handle, &entry->common, &dbkey, &dbitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    PORT_FreeArena(tmparena, PR_FALSE);
+    return(SECSuccess);
+
+loser:
+    if ( tmparena ) {
+	PORT_FreeArena(tmparena, PR_FALSE);
+    }
+    return(SECFailure);
+}
+
+/*
+ * delete a content version entry
+ */
+static SECStatus
+DeleteDBContentVersionEntry(CERTCertDBHandle *handle)
+{
+    SECItem dbkey;
+    PRArenaPool *arena = NULL;
+    SECStatus rv;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+
+    /* now get the database key and format it */
+    dbkey.len = SEC_DB_CONTENT_VERSION_KEY_LEN + SEC_DB_KEY_HEADER_LEN;
+    dbkey.data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey.len);
+    if ( dbkey.data == NULL ) {
+	goto loser;
+    }
+    PORT_Memcpy(&dbkey.data[SEC_DB_KEY_HEADER_LEN], SEC_DB_CONTENT_VERSION_KEY,
+		SEC_DB_CONTENT_VERSION_KEY_LEN);
+    
+    rv = DeleteDBEntry(handle, certDBEntryTypeContentVersion, &dbkey);
+    if ( rv == SECFailure ) {
+	goto loser;
+    }
+
+    PORT_FreeArena(arena, PR_FALSE);
+    return(SECSuccess);
+
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(SECFailure);
+}
+
+/*
+ * Routines and datastructures to manage the list of certificates for a
+ * particular subject name.
+ */
+
+/*
+ * Create a new certificate subject list.  If entry exists, then populate
+ * the list with the entries from the permanent database.
+ */
+static CERTSubjectList *
+NewSubjectList(certDBEntrySubject *entry)
+{
+    PRArenaPool *permarena;
+    unsigned int i;
+    CERTSubjectList *subjectList;
+    CERTSubjectNode *node;
+    SECStatus rv;
+    
+    permarena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( permarena == NULL ) {
+	goto loser;
+    }
+    subjectList = (CERTSubjectList *)PORT_ArenaAlloc(permarena,
+						     sizeof(CERTSubjectList));
+    if ( subjectList == NULL ) {
+	goto loser;
+    }
+
+    subjectList->arena = permarena;
+    subjectList->ncerts = 0;
+    subjectList->head = NULL;
+    subjectList->tail = NULL;
+    subjectList->entry = entry;
+    subjectList->emailAddr = NULL;
+    if ( entry ) {
+	
+	/* initialize the list with certs from database entry */
+	for ( i = 0; i < entry->ncerts; i++ ) {
+	    /* Init the node */
+	    node = (CERTSubjectNode *)PORT_ArenaAlloc(permarena,
+						      sizeof(CERTSubjectNode));
+	    if ( node == NULL ) {
+		goto loser;
+	    }
+
+	    /* copy certKey and keyID to node */
+	    rv = SECITEM_CopyItem(permarena, &node->certKey,
+				  &entry->certKeys[i]);
+	    if ( rv != SECSuccess ) {
+		goto loser;
+	    }
+	    rv = SECITEM_CopyItem(permarena, &node->keyID,
+				  &entry->keyIDs[i]);
+	    if ( rv != SECSuccess ) {
+		goto loser;
+	    }
+
+	    /* the certs are already in order, so just add them
+	     * to the tail.
+	     */
+	    node->next = NULL;
+	    if ( subjectList->tail == NULL ) {
+		/* first in list */
+		subjectList->head = node;
+		subjectList->tail = node;
+		node->prev = NULL;
+	    } else {
+		/* add to end of list */
+		node->prev = subjectList->tail;
+		subjectList->tail = node;
+		node->prev->next = node;
+	    }
+	    subjectList->ncerts++;
+	}
+    }
+    
+    return(subjectList);
+
+loser:
+    PORT_FreeArena(permarena, PR_FALSE);
+    return(NULL);
+}
+
+/*
+ * Find the Subject entry in the temp database.  It it is not in the
+ * temp database, then get it from the perm DB.  It its not there either,
+ * then create a new one.
+ */
+static CERTSubjectList *
+FindSubjectList(CERTCertDBHandle *handle, SECItem *subject, PRBool create)
+{
+    PRArenaPool *arena = NULL;
+    SECItem keyitem;
+    SECStatus rv;
+    DBT namekey;
+    DBT tmpdata;
+    int ret;
+    CERTSubjectList *subjectList = NULL;
+    certDBEntrySubject *entry;
+
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+
+    rv = EncodeDBSubjectKey(subject, arena, &keyitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    namekey.data = keyitem.data;
+    namekey.size = keyitem.len;
+    
+    /* lookup in the temporary database */
+    ret = certdb_Get(handle->tempCertDB, &namekey, &tmpdata, 0);
+
+    /* error accessing the database */
+    if ( ret < 0 ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+
+    if ( ret == 0 ) {	/* found in temp database */
+	if ( tmpdata.size != sizeof(CERTCertificate *) ) {
+	    PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	    goto loser;
+	}
+
+	/* copy pointer out of database */
+	PORT_Memcpy(&subjectList, tmpdata.data, tmpdata.size);
+    } else {		/* not found in temporary database */
+	entry = ReadDBSubjectEntry(handle, subject);
+	if ( entry || create ) {
+	    /* decode or create new subject list */
+	    subjectList = NewSubjectList(entry);
+
+	    /* put it in the temp database */
+	    if ( subjectList ) {
+		tmpdata.data = (unsigned char *)(&subjectList);
+		tmpdata.size = sizeof(subjectList);
+		ret = certdb_Put(handle->tempCertDB, &namekey,
+				 &tmpdata, R_NOOVERWRITE);
+		if ( ret ) {
+		    goto loser;
+		}
+	    }
+	} else {
+	    PORT_SetError(SEC_ERROR_UNKNOWN_CERT);
+	    goto loser;
+	}
+    }
+
+    goto done;
+
+loser:
+    subjectList = NULL;
+    
+done:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(subjectList);
+}
+
+/*
+ * Add a temp cert to the temp subject list
+ */
+static SECStatus
+AddTempCertToSubjectList(CERTCertificate *cert)
+{
+    CERTSubjectList *subjectList;
+    CERTSubjectNode *node, *newnode;
+    CERTCertificate *cmpcert;
+    PRBool newer;
+    SECStatus rv;
+    
+    PORT_Assert(cert->isperm == PR_FALSE);
+    PORT_Assert(cert->subjectList == NULL);
+    
+    subjectList = FindSubjectList(cert->dbhandle, &cert->derSubject, PR_TRUE);
+    
+    if ( subjectList == NULL ) {
+	goto loser;
+    }
+
+    newnode = (CERTSubjectNode*)PORT_ArenaAlloc(subjectList->arena,
+						sizeof(CERTSubjectNode));
+    /* copy certKey and keyID to node */
+    rv = SECITEM_CopyItem(subjectList->arena, &newnode->certKey,
+			  &cert->certKey);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    rv = SECITEM_CopyItem(subjectList->arena, &newnode->keyID,
+			  &cert->subjectKeyID);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+
+    node = subjectList->head;
+
+    if ( node ) {
+	/* list is not empty */
+	while ( node ) {
+	    cmpcert = CERT_FindCertByKeyNoLocking(cert->dbhandle,
+						  &node->certKey);
+	    if ( cmpcert ) {
+		
+		newer =  CERT_IsNewer(cert, cmpcert);
+		CERT_DestroyCertificateNoLocking(cmpcert);
+		if ( newer ) {
+		    /* insert before this cert */
+		    newnode->next = node;
+		    newnode->prev = node->prev;
+		    if ( newnode->prev ) {
+			newnode->prev->next = newnode;
+		    } else {
+			/* at the head of the list */
+			subjectList->head = newnode;
+		    }
+		    node->prev = newnode;
+		    goto done;
+		}
+	    }
+	    node = node->next;
+	}
+	/* if we get here, we add the node to the end of the list */
+	newnode->prev = subjectList->tail;
+	newnode->next = NULL;
+	subjectList->tail->next = newnode;
+	subjectList->tail = newnode;
+    } else {
+	/* this is a new/empty list */
+	newnode->next = NULL;
+	newnode->prev = NULL;
+	subjectList->head = newnode;
+	subjectList->tail = newnode;
+    }
+    
+done:
+    subjectList->ncerts++;
+    cert->subjectList = subjectList;
+    return(SECSuccess);
+    
+loser:
+    return(SECFailure);
+}
+
+/*
+ * Find the node in a subjectList that belongs a cert
+ */
+static CERTSubjectNode *
+FindCertSubjectNode(CERTCertificate *cert)
+{
+    CERTSubjectList *subjectList;
+    CERTSubjectNode *node = NULL;
+    
+    PORT_Assert(cert->subjectList);
+    
+    subjectList = cert->subjectList;
+    
+    if ( subjectList ) {
+	node = subjectList->head;
+    }
+    
+    while ( node ) {
+	if ( SECITEM_CompareItem(&node->certKey, &cert->certKey) == SECEqual ){
+	    return(node);
+	    break;
+	}
+	
+	node = node->next;
+    }
+
+    return(NULL);
+}
+
+/*
+ * Remove a temp cert from the temp subject list
+ */
+static SECStatus
+RemoveTempCertFromSubjectList(CERTCertificate *cert)
+{
+    CERTSubjectList *subjectList;
+    CERTSubjectNode *node;
+    SECItem keyitem;
+    DBT namekey;
+    SECStatus rv;
+    int ret;
+    CERTCertDBHandle *handle;
+    
+    PORT_Assert(cert->subjectList);
+
+    /* don't remove perm certs */
+    if ( cert->isperm ) {
+	return(SECSuccess);
+    }
+    
+    subjectList = cert->subjectList;
+
+    node = FindCertSubjectNode(cert);
+    
+    if ( node ) {
+	/* found it, unlink it */
+	if ( node->next ) {
+	    node->next->prev = node->prev;
+	} else {
+	    /* removing from tail of list */
+	    subjectList->tail = node->prev;
+	}
+	if ( node->prev ) {
+	    node->prev->next = node->next;
+	} else {
+	    /* removing from head of list */
+	    subjectList->head = node->next;
+	}
+
+	subjectList->ncerts--;
+	
+	/* dont need to free the node, because it is from subjectList
+	 * arena.
+	 */
+	
+	/* remove reference from cert */
+	cert->subjectList = NULL;
+	
+	/* if the list is now empty, remove the list from the db and free it */
+	if ( subjectList->head == NULL ) {
+	    PORT_Assert(subjectList->ncerts == 0);
+	    rv = EncodeDBSubjectKey(&cert->derSubject, subjectList->arena,
+				    &keyitem);
+	    if ( rv == SECSuccess ) {
+		namekey.data = keyitem.data;
+		namekey.size = keyitem.len;
+
+		handle = cert->dbhandle;
+		
+		ret = certdb_Del(handle->tempCertDB, &namekey, 0);
+		/* keep going if it fails */
+
+		if ( cert->dbnickname ) {
+		    rv = SEC_DeleteTempNickname(handle, cert->dbnickname);
+		} else if ( cert->nickname ) {
+		    rv = SEC_DeleteTempNickname(handle, cert->nickname);
+		}
+		
+		/* keep going if it fails */
+	    }
+
+	    PORT_FreeArena(subjectList->arena, PR_FALSE);
+	}
+    }
+
+    PORT_Assert(cert->subjectList == NULL);
+    
+    if ( cert->subjectList != NULL ) {
+	return(SECFailure);
+    }
+
+    return(SECSuccess);
+}
+
+/*
+ * cert is no longer a perm cert, but will remain a temp cert
+ */
+static SECStatus
+RemovePermSubjectNode(CERTCertificate *cert)
+{
+    CERTSubjectList *subjectList;
+    certDBEntrySubject *entry;
+    unsigned int i;
+    SECStatus rv;
+
+    PORT_Assert(cert->isperm);
+    if ( !cert->isperm ) {
+	return(SECFailure);
+    }
+    
+    subjectList = cert->subjectList;
+    PORT_Assert(subjectList);
+    if ( subjectList == NULL ) {
+	return(SECFailure);
+    }
+    entry = subjectList->entry;
+    PORT_Assert(entry);
+    if ( entry == NULL ) {
+	return(SECFailure);
+    }
+
+    PORT_Assert(entry->ncerts);
+    rv = SECFailure;
+    
+    if ( entry->ncerts > 1 ) {
+	for ( i = 0; i < entry->ncerts; i++ ) {
+	    if ( SECITEM_CompareItem(&entry->certKeys[i], &cert->certKey) ==
+		SECEqual ) {
+		/* copy rest of list forward one entry */
+		for ( i = i + 1; i < entry->ncerts; i++ ) {
+		    entry->certKeys[i-1] = entry->certKeys[i];
+		    entry->keyIDs[i-1] = entry->keyIDs[i];
+		}
+		entry->ncerts--;
+		DeleteDBSubjectEntry(cert->dbhandle, &cert->derSubject);
+		rv = WriteDBSubjectEntry(cert->dbhandle, entry);
+		break;
+	    }
+	}
+    } else {
+	/* no entries left, delete the perm entry in the DB */
+	if ( subjectList->entry->emailAddr ) {
+	    /* if the subject had an email record, then delete it too */
+	    DeleteDBSMimeEntry(cert->dbhandle, subjectList->entry->emailAddr);
+	}
+	
+	DestroyDBEntry((certDBEntry *)subjectList->entry);
+	subjectList->entry = NULL;
+	DeleteDBSubjectEntry(cert->dbhandle, &cert->derSubject);
+    }
+
+    return(rv);
+}
+
+/*
+ * add a cert to the perm subject list
+ */
+static SECStatus
+AddPermSubjectNode(CERTCertificate *cert, char *nickname)
+{
+    CERTSubjectList *subjectList;
+    certDBEntrySubject *entry;
+    SECItem *newCertKeys, *newKeyIDs;
+    int i;
+    SECStatus rv;
+    CERTCertificate *cmpcert;
+    unsigned int nnlen;
+    int ncerts;
+    
+    subjectList = cert->subjectList;
+    
+    PORT_Assert(subjectList);
+    if ( subjectList == NULL ) {
+	return(SECFailure);
+    }
+
+    entry = subjectList->entry;
+    
+    if ( entry ) {
+	ncerts = entry->ncerts;
+	
+	if ( nickname && entry->nickname ) {
+	    /* nicknames must be the same */
+	    PORT_Assert(PORT_Strcmp(nickname, entry->nickname) == 0);
+	}
+
+	if ( ( entry->nickname == NULL ) && ( nickname != NULL ) ) {
+	    /* copy nickname into the entry */
+	    nnlen = PORT_Strlen(nickname) + 1;
+	    entry->nickname = (char *)PORT_ArenaAlloc(entry->common.arena,
+						      nnlen);
+	    if ( entry->nickname == NULL ) {
+		return(SECFailure);
+	    }
+	    PORT_Memcpy(entry->nickname, nickname, nnlen);
+	}
+	
+	/* a DB entry already exists, so add this cert */
+	newCertKeys = (SECItem *)PORT_ArenaAlloc(entry->common.arena,
+						 sizeof(SECItem) *
+						 ( ncerts + 1 ) );
+	newKeyIDs = (SECItem *)PORT_ArenaAlloc(entry->common.arena,
+					       sizeof(SECItem) *
+					       ( ncerts + 1 ) );
+
+	if ( ( newCertKeys == NULL ) || ( newKeyIDs == NULL ) ) {
+	    return(SECFailure);
+	}
+
+	for ( i = 0; i < ncerts; i++ ) {
+	    cmpcert = CERT_FindCertByKeyNoLocking(cert->dbhandle,
+						  &entry->certKeys[i]);
+	    PORT_Assert(cmpcert);
+	    
+	    if ( CERT_IsNewer(cert, cmpcert) ) {
+		/* insert before cmpcert */
+		rv = SECITEM_CopyItem(entry->common.arena, &newCertKeys[i],
+				      &cert->certKey);
+		if ( rv != SECSuccess ) {
+		    return(SECFailure);
+		}
+		rv = SECITEM_CopyItem(entry->common.arena, &newKeyIDs[i],
+				      &cert->subjectKeyID);
+		if ( rv != SECSuccess ) {
+		    return(SECFailure);
+		}
+		/* copy the rest of the entry */
+		for ( ; i < ncerts; i++ ) {
+		    newCertKeys[i+1] = entry->certKeys[i];
+		    newKeyIDs[i+1] = entry->keyIDs[i];
+		}
+
+		/* update certKeys and keyIDs */
+		entry->certKeys = newCertKeys;
+		entry->keyIDs = newKeyIDs;
+		
+		/* increment count */
+		entry->ncerts++;
+		break;
+	    }
+	    /* copy this cert entry */
+	    newCertKeys[i] = entry->certKeys[i];
+	    newKeyIDs[i] = entry->keyIDs[i];
+	}
+
+	if ( entry->ncerts == ncerts ) {
+	    /* insert new one at end */
+	    rv = SECITEM_CopyItem(entry->common.arena, &newCertKeys[ncerts],
+				  &cert->certKey);
+	    if ( rv != SECSuccess ) {
+		return(SECFailure);
+	    }
+	    rv = SECITEM_CopyItem(entry->common.arena, &newKeyIDs[ncerts],
+				  &cert->subjectKeyID);
+	    if ( rv != SECSuccess ) {
+		return(SECFailure);
+	    }
+
+	    /* update certKeys and keyIDs */
+	    entry->certKeys = newCertKeys;
+	    entry->keyIDs = newKeyIDs;
+		
+	    /* increment count */
+	    entry->ncerts++;
+	}
+    } else {
+	/* need to make a new DB entry */
+	entry = NewDBSubjectEntry(&cert->derSubject, &cert->certKey,
+				  &cert->subjectKeyID, nickname,
+				  NULL, 0);
+	cert->subjectList->entry = entry;
+    }
+    if ( entry ) {
+	DeleteDBSubjectEntry(cert->dbhandle, &cert->derSubject);
+	rv = WriteDBSubjectEntry(cert->dbhandle, entry);
+    } else {
+	rv = SECFailure;
+    }
+    
+    return(rv);
+}
+
+
+
+SECStatus
+__CERT_TraversePermCertsForSubject(CERTCertDBHandle *handle,
+				 SECItem *derSubject,
+				 CERTCertCallback cb, void *cbarg)
+{
+    certDBEntrySubject *entry;
+    int i;
+    CERTCertificate *cert;
+    SECStatus rv = SECSuccess;
+    
+    entry = ReadDBSubjectEntry(handle, derSubject);
+
+    if ( entry == NULL ) {
+	return(SECFailure);
+    }
+    
+    for( i = 0; i < entry->ncerts; i++ ) {
+	cert = CERT_FindCertByKey(handle, &entry->certKeys[i]);
+	rv = (* cb)(cert, cbarg);
+	CERT_DestroyCertificate(cert);
+	if ( rv == SECFailure ) {
+	    break;
+	}
+    }
+
+    DestroyDBEntry((certDBEntry *)entry);
+
+    return(rv);
+}
+
+SECStatus
+CERT_TraversePermCertsForSubject(CERTCertDBHandle *handle, SECItem *derSubject,
+				 CERTCertCallback cb, void *cbarg)
+{
+    return(__CERT_TraversePermCertsForSubject(handle, derSubject, cb, cbarg));
+}
+
+int
+CERT_NumPermCertsForSubject(CERTCertDBHandle *handle, SECItem *derSubject)
+{
+    certDBEntrySubject *entry;
+    int ret;
+    
+    entry = ReadDBSubjectEntry(handle, derSubject);
+
+    if ( entry == NULL ) {
+	return(SECFailure);
+    }
+
+    ret = entry->ncerts;
+    
+    DestroyDBEntry((certDBEntry *)entry);
+    
+    return(ret);
+}
+
+SECStatus
+__CERT_TraversePermCertsForNickname(CERTCertDBHandle *handle, char *nickname,
+				  CERTCertCallback cb, void *cbarg)
+{
+    certDBEntryNickname *nnentry = NULL;
+    certDBEntrySMime *smentry = NULL;
+    SECStatus rv;
+    SECItem *derSubject = NULL;
+    
+    nnentry = ReadDBNicknameEntry(handle, nickname);
+    if ( nnentry ) {
+	derSubject = &nnentry->subjectName;
+    } else {
+	smentry = ReadDBSMimeEntry(handle, nickname);
+	if ( smentry ) {
+	    derSubject = &smentry->subjectName;
+	}
+    }
+    
+    if ( derSubject ) {
+	rv = CERT_TraversePermCertsForSubject(handle, derSubject,
+					      cb, cbarg);
+    } else {
+	rv = SECFailure;
+    }
+
+    if ( nnentry ) {
+	DestroyDBEntry((certDBEntry *)nnentry);
+    }
+    if ( smentry ) {
+	DestroyDBEntry((certDBEntry *)smentry);
+    }
+    
+    return(rv);
+}
+
+SECStatus
+CERT_TraversePermCertsForNickname(CERTCertDBHandle *handle, char *nickname,
+				  CERTCertCallback cb, void *cbarg)
+{
+    return(__CERT_TraversePermCertsForNickname(handle, nickname, cb, cbarg));
+}
+
+int
+CERT_NumPermCertsForNickname(CERTCertDBHandle *handle, char *nickname)
+{
+    certDBEntryNickname *entry;
+    int ret;
+    
+    entry = ReadDBNicknameEntry(handle, nickname);
+    
+    if ( entry ) {
+	ret = CERT_NumPermCertsForSubject(handle, &entry->subjectName);
+	DestroyDBEntry((certDBEntry *)entry);
+    } else {
+	ret = 0;
+    }
+    return(ret);
+}
+
+int
+CERT_NumCertsForCertSubject(CERTCertificate *cert)
+{
+    int ret = 0;
+    
+    if ( cert->subjectList ) {
+	ret = cert->subjectList->ncerts;
+    }
+    return(ret);
+}
+
+int
+CERT_NumPermCertsForCertSubject(CERTCertificate *cert)
+{
+    int ret = 0;
+    
+    if ( cert->subjectList ) {
+	if ( cert->subjectList->entry ) {
+	    ret = cert->subjectList->entry->ncerts;
+	}
+    }
+    return(ret);
+}
+
+SECStatus
+CERT_TraverseCertsForSubject(CERTCertDBHandle *handle,
+			     CERTSubjectList *subjectList,
+			     CERTCertCallback cb, void *cbarg)
+{
+    CERTSubjectNode *node;
+    CERTCertificate *cert;
+    SECStatus rv = SECSuccess;
+    
+    CERT_LockDB(handle);
+
+    node = subjectList->head;
+    while ( node ) {
+
+	cert = CERT_FindCertByKeyNoLocking(handle, &node->certKey);
+
+	PORT_Assert(cert != NULL);
+
+	if ( cert != NULL ) {
+	    rv = (* cb)(cert, cbarg);
+	    CERT_DestroyCertificateNoLocking(cert);
+	    if ( rv == SECFailure ) {
+		break;
+	    }
+	}
+
+	node = node->next;
+    }
+
+    CERT_UnlockDB(handle);
+
+    return(rv);
+}
+
+
+/*
+ * Given a cert, find the cert with the same subject name that
+ * has the given key usage.  If the given cert has the correct keyUsage, then
+ * return it, otherwise search the list in order.
+ */
+CERTCertificate *
+CERT_FindCertByUsage(CERTCertificate *basecert, unsigned int requiredKeyUsage)
+{
+    CERTSubjectNode *node;
+    CERTCertificate *cert;
+    CERTSubjectList *subjectList;
+    
+    if ( ( basecert->keyUsage & requiredKeyUsage ) == requiredKeyUsage ) {
+	return(CERT_DupCertificate(basecert));
+    }
+    
+    CERT_LockDB(basecert->dbhandle);
+
+    subjectList = basecert->subjectList;
+	
+    node = subjectList->head;
+    while ( node ) {
+
+	cert = CERT_FindCertByKeyNoLocking(basecert->dbhandle, &node->certKey);
+
+	PORT_Assert(cert != NULL);
+
+	if ( cert != NULL ) {
+	    if ( ( cert->keyUsage & requiredKeyUsage ) ==
+		 requiredKeyUsage ) {
+		CERT_UnlockDB(basecert->dbhandle);
+		return(cert);
+	    }
+	
+	    CERT_DestroyCertificateNoLocking(cert);
+	}
+
+	node = node->next;
+    }
+
+    CERT_UnlockDB(basecert->dbhandle);
+
+    return(NULL);
+}
+
+
+/*
+ * add a nickname to a cert that doesn't have one
+ */
+static SECStatus
+AddNicknameToPermCert(CERTCertificate *cert, char *nickname)
+{
+    certDBEntryCert *entry;
+    int rv;
+    
+    PORT_Assert(cert->isperm);
+    if ( !cert->isperm ) {
+	goto loser;
+    }
+
+    entry = cert->dbEntry;
+    PORT_Assert(entry != NULL);
+    if ( entry == NULL ) {
+	goto loser;
+    }
+
+    entry->nickname = PORT_ArenaStrdup(entry->common.arena, nickname);
+
+    rv = WriteDBCertEntry(cert->dbhandle, entry);
+    if ( rv ) {
+	goto loser;
+    }
+
+    cert->nickname = PORT_ArenaStrdup(cert->arena, nickname);
+    return(SECSuccess);
+    
+loser:
+    return(SECFailure);
+}
+
+/*
+ * add a nickname to a cert that is already in the perm database, but doesn't
+ * have one yet (it is probably an e-mail cert).
+ */
+SECStatus
+CERT_AddPermNickname(CERTCertificate *cert, char *nickname)
+{
+    SECStatus rv;
+    
+    CERT_LockDB(cert->dbhandle);
+    
+    PORT_Assert(cert->nickname == NULL);
+    PORT_Assert(cert->isperm);
+    PORT_Assert(cert->subjectList != NULL);
+    PORT_Assert(cert->subjectList->entry != NULL);
+    
+    if ( cert->nickname != NULL ) {
+	goto done;
+    }
+
+    if ( cert->subjectList == NULL ) {
+	goto loser;
+    }
+    
+    if ( cert->subjectList->entry == NULL ) {
+	goto loser;
+    }
+
+    if ( cert->subjectList->entry->nickname == NULL ) {
+	/* no nickname for subject */
+	rv = AddNicknameToSubject(cert, nickname);
+	if ( rv != SECSuccess ) {
+	    goto loser;
+	}
+	rv = AddNicknameToPermCert(cert, nickname);
+	if ( rv != SECSuccess ) {
+	    goto loser;
+	}
+	rv = SEC_AddTempNickname(cert->dbhandle, nickname,
+				 &cert->derSubject);
+	if ( rv != SECSuccess ) {
+	    goto loser;
+	}
+
+    } else {
+	/* subject already has a nickname */
+	rv = AddNicknameToPermCert(cert, cert->subjectList->entry->nickname);
+	if ( rv != SECSuccess ) {
+	    goto loser;
+	}
+    }
+
+done:
+    CERT_UnlockDB(cert->dbhandle);
+    return(SECSuccess);
+loser:
+    CERT_UnlockDB(cert->dbhandle);
+    return(SECFailure);
+}
+
+static certDBEntryCert *
+AddCertToPermDB(CERTCertDBHandle *handle, CERTCertificate *cert,
+		char *nickname, CERTCertTrust *trust)
+{
+    certDBEntryCert *certEntry = NULL;
+    certDBEntryNickname *nicknameEntry = NULL;
+    certDBEntrySubject *subjectEntry = NULL;
+    int state = 0;
+    SECStatus rv;
+    PRBool donnentry = PR_FALSE;
+
+    if ( nickname ) {
+	donnentry = PR_TRUE;
+    }
+	
+    if ( cert->subjectList != NULL ) {
+	if ( cert->subjectList->entry != NULL ) {
+	    if ( cert->subjectList->entry->ncerts > 0 ) {
+		/* of other certs with same subject exist, then they already
+		 * have a nickname, so don't add a new one.
+		 */
+		donnentry = PR_FALSE;
+		nickname = cert->subjectList->entry->nickname;
+	    }
+	}
+    }
+    
+    certEntry = NewDBCertEntry(&cert->derCert, nickname, trust, 0);
+    if ( certEntry == NULL ) {
+	goto loser;
+    }
+    
+    if ( donnentry ) {
+	nicknameEntry = NewDBNicknameEntry(nickname, &cert->derSubject, 0);
+	if ( nicknameEntry == NULL ) {
+	    goto loser;
+	}
+    }
+    
+    rv = WriteDBCertEntry(handle, certEntry);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    state = 1;
+    
+    if ( nicknameEntry ) {
+	rv = WriteDBNicknameEntry(handle, nicknameEntry);
+	if ( rv != SECSuccess ) {
+	    goto loser;
+	}
+    }
+    
+    state = 2;
+    
+    /* add to or create new subject entry */
+    if ( cert->subjectList ) {
+	rv = AddPermSubjectNode(cert, nickname);
+	if ( rv != SECSuccess ) {
+	    goto loser;
+	}
+    } else {
+	/* make a new subject entry - this case is only used when updating
+	 * an old version of the database.  This is OK because the oldnickname
+	 * db format didn't allow multiple certs with the same subject.
+	 */
+	subjectEntry = NewDBSubjectEntry(&cert->derSubject, &cert->certKey,
+					 &cert->subjectKeyID, nickname,
+					 NULL, 0);
+	if ( subjectEntry == NULL ) {
+	    goto loser;
+	}
+	rv = WriteDBSubjectEntry(handle, subjectEntry);
+	if ( rv != SECSuccess ) {
+	    goto loser;
+	}
+    }
+    
+    state = 3;
+    
+    if ( nicknameEntry ) {
+	DestroyDBEntry((certDBEntry *)nicknameEntry);
+    }
+    
+    if ( subjectEntry ) {
+	DestroyDBEntry((certDBEntry *)subjectEntry);
+    }
+
+    return(certEntry);
+
+loser:
+    /* don't leave partial entry in the database */
+    if ( state > 0 ) {
+	rv = DeleteDBCertEntry(handle, &cert->certKey);
+    }
+    if ( ( state > 1 ) && donnentry ) {
+	rv = DeleteDBNicknameEntry(handle, nickname);
+    }
+    if ( state > 2 ) {
+	rv = DeleteDBSubjectEntry(handle, &cert->derSubject);
+    }
+    if ( certEntry ) {
+	DestroyDBEntry((certDBEntry *)certEntry);
+    }
+    if ( nicknameEntry ) {
+	DestroyDBEntry((certDBEntry *)nicknameEntry);
+    }
+    if ( subjectEntry ) {
+	DestroyDBEntry((certDBEntry *)subjectEntry);
+    }
+
+    return(NULL);
+}
+
+/*
+ * NOTE - Version 6 DB did not go out to the real world in a release,
+ * so we can remove this function in a later release.
+ */
+static SECStatus
+UpdateV6DB(CERTCertDBHandle *handle, DB *updatedb)
+{
+    int ret;
+    DBT key, data;
+    unsigned char *buf, *tmpbuf = NULL;
+    certDBEntryType type;
+    certDBEntryNickname *nnEntry = NULL;
+    certDBEntrySubject *subjectEntry = NULL;
+    certDBEntrySMime *emailEntry = NULL;
+    char *nickname;
+    char *emailAddr;
+    SECStatus rv;
+    
+    /*
+     * Sequence through the old database and copy all of the entries
+     * to the new database.  Subject name entries will have the new
+     * fields inserted into them (with zero length).
+     */
+    ret = (* updatedb->seq)(updatedb, &key, &data, R_FIRST);
+    if ( ret ) {
+	return(SECFailure);
+    }
+
+    do {
+	buf = (unsigned char *)data.data;
+	
+	if ( data.size >= 3 ) {
+	    if ( buf[0] == 6 ) { /* version number */
+		type = (certDBEntryType)buf[1];
+		if ( type == certDBEntryTypeSubject ) {
+		    /* expando subjecto entrieo */
+		    tmpbuf = (unsigned char *)PORT_Alloc(data.size + 4);
+		    if ( tmpbuf ) {
+			/* copy header stuff */
+			PORT_Memcpy(tmpbuf, buf, SEC_DB_ENTRY_HEADER_LEN + 2);
+			/* insert 4 more bytes of zero'd header */
+			PORT_Memset(&tmpbuf[SEC_DB_ENTRY_HEADER_LEN + 2],
+				    0, 4);
+			/* copy rest of the data */
+			PORT_Memcpy(&tmpbuf[SEC_DB_ENTRY_HEADER_LEN + 6],
+				    &buf[SEC_DB_ENTRY_HEADER_LEN + 2],
+				    data.size - (SEC_DB_ENTRY_HEADER_LEN + 2));
+
+			data.data = (void *)tmpbuf;
+			data.size += 4;
+			buf = tmpbuf;
+		    }
+		} else if ( type == certDBEntryTypeCert ) {
+		    /* expando certo entrieo */
+		    tmpbuf = (unsigned char *)PORT_Alloc(data.size + 3);
+		    if ( tmpbuf ) {
+			/* copy header stuff */
+			PORT_Memcpy(tmpbuf, buf, SEC_DB_ENTRY_HEADER_LEN);
+
+			/* copy trust flage, setting msb's to 0 */
+			tmpbuf[SEC_DB_ENTRY_HEADER_LEN] = 0;
+			tmpbuf[SEC_DB_ENTRY_HEADER_LEN+1] =
+			    buf[SEC_DB_ENTRY_HEADER_LEN];
+			tmpbuf[SEC_DB_ENTRY_HEADER_LEN+2] = 0;
+			tmpbuf[SEC_DB_ENTRY_HEADER_LEN+3] =
+			    buf[SEC_DB_ENTRY_HEADER_LEN+1];
+			tmpbuf[SEC_DB_ENTRY_HEADER_LEN+4] = 0;
+			tmpbuf[SEC_DB_ENTRY_HEADER_LEN+5] =
+			    buf[SEC_DB_ENTRY_HEADER_LEN+2];
+			
+			/* copy rest of the data */
+			PORT_Memcpy(&tmpbuf[SEC_DB_ENTRY_HEADER_LEN + 6],
+				    &buf[SEC_DB_ENTRY_HEADER_LEN + 3],
+				    data.size - (SEC_DB_ENTRY_HEADER_LEN + 3));
+
+			data.data = (void *)tmpbuf;
+			data.size += 3;
+			buf = tmpbuf;
+		    }
+
+		}
+
+		/* update the record version number */
+		buf[0] = CERT_DB_FILE_VERSION;
+
+		/* copy to the new database */
+		ret = certdb_Put(handle->permCertDB, &key, &data, 0);
+		if ( tmpbuf ) {
+		    PORT_Free(tmpbuf);
+		    tmpbuf = NULL;
+		}
+	    }
+	}
+    } while ( (* updatedb->seq)(updatedb, &key, &data, R_NEXT) == 0 );
+
+    ret = certdb_Sync(handle->permCertDB, 0);
+
+    ret = (* updatedb->seq)(updatedb, &key, &data, R_FIRST);
+    if ( ret ) {
+	return(SECFailure);
+    }
+
+    do {
+	buf = (unsigned char *)data.data;
+	
+	if ( data.size >= 3 ) {
+	    if ( buf[0] == CERT_DB_FILE_VERSION ) { /* version number */
+		type = (certDBEntryType)buf[1];
+		if ( type == certDBEntryTypeNickname ) {
+		    nickname = &((char *)key.data)[1];
+
+		    /* get the matching nickname entry in the new DB */
+		    nnEntry = ReadDBNicknameEntry(handle, nickname);
+		    if ( nnEntry == NULL ) {
+			goto endloop;
+		    }
+		    
+		    /* find the subject entry pointed to by nickname */
+		    subjectEntry = ReadDBSubjectEntry(handle,
+						      &nnEntry->subjectName);
+		    if ( subjectEntry == NULL ) {
+			goto endloop;
+		    }
+		    
+		    subjectEntry->nickname =
+			(char *)PORT_ArenaAlloc(subjectEntry->common.arena,
+						key.size - 1);
+		    if ( subjectEntry->nickname ) {
+			PORT_Memcpy(subjectEntry->nickname, nickname,
+				    key.size - 1);
+			rv = WriteDBSubjectEntry(handle, subjectEntry);
+		    }
+		} else if ( type == certDBEntryTypeSMimeProfile ) {
+		    emailAddr = &((char *)key.data)[1];
+
+		    /* get the matching smime entry in the new DB */
+		    emailEntry = ReadDBSMimeEntry(handle, emailAddr);
+		    if ( emailEntry == NULL ) {
+			goto endloop;
+		    }
+		    
+		    /* find the subject entry pointed to by nickname */
+		    subjectEntry = ReadDBSubjectEntry(handle,
+						      &emailEntry->subjectName);
+		    if ( subjectEntry == NULL ) {
+			goto endloop;
+		    }
+		    
+		    subjectEntry->nickname =
+			(char *)PORT_ArenaAlloc(subjectEntry->common.arena,
+						key.size - 1);
+		    if ( subjectEntry->emailAddr ) {
+			PORT_Memcpy(subjectEntry->emailAddr, emailAddr,
+				    key.size - 1);
+			rv = WriteDBSubjectEntry(handle, subjectEntry);
+		    }
+		}
+		
+endloop:
+		if ( subjectEntry ) {
+		    DestroyDBEntry((certDBEntry *)subjectEntry);
+		    subjectEntry = NULL;
+		}
+		if ( nnEntry ) {
+		    DestroyDBEntry((certDBEntry *)nnEntry);
+		    nnEntry = NULL;
+		}
+		if ( emailEntry ) {
+		    DestroyDBEntry((certDBEntry *)emailEntry);
+		    emailEntry = NULL;
+		}
+	    }
+	}
+    } while ( (* updatedb->seq)(updatedb, &key, &data, R_NEXT) == 0 );
+
+    ret = certdb_Sync(handle->permCertDB, 0);
+
+    (* updatedb->close)(updatedb);
+    return(SECSuccess);
+}
+
+
+static SECStatus
+updateV5Callback(CERTCertificate *cert, SECItem *k, void *pdata)
+{
+    CERTCertDBHandle *handle;
+    certDBEntryCert *entry;
+    CERTCertTrust *trust;
+    
+    handle = (CERTCertDBHandle *)pdata;
+    trust = &cert->dbEntry->trust;
+
+    /* SSL user certs can be used for email if they have an email addr */
+    if ( cert->emailAddr && ( trust->sslFlags & CERTDB_USER ) &&
+	( trust->emailFlags == 0 ) ) {
+	trust->emailFlags = CERTDB_USER;
+    }
+    /* 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(CERTCertDBHandle *handle, DB *updatedb)
+{
+    CERTCertDBHandle updatehandle;
+    SECStatus rv;
+    
+    updatehandle.permCertDB = updatedb;
+    updatehandle.dbMon = PZ_NewMonitor(nssILockCertDB);
+    
+    rv = SEC_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(CERTCertDBHandle *handle, DB *updatedb)
+{
+    DBT key, data;
+    certDBEntryCert *entry, *entry2;
+    SECItem derSubject;
+    int ret;
+    PRArenaPool *arena = NULL;
+    CERTCertificate *cert;
+
+    ret = (* updatedb->seq)(updatedb, &key, &data, R_FIRST);
+
+    if ( ret ) {
+	return(SECFailure);
+    }
+
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if (arena == NULL) {
+	return(SECFailure);
+    }
+    
+    do {
+	if ( data.size != 1 ) { /* skip version number */
+
+	    /* decode the old DB entry */
+	    entry = (certDBEntryCert *)DecodeV4DBCertEntry((unsigned char*)data.data, data.size);
+	    derSubject.data = NULL;
+	    
+	    if ( entry ) {
+		cert = CERT_DecodeDERCertificate(&entry->derCert, PR_TRUE,
+						 entry->nickname);
+
+		if ( cert != NULL ) {
+		    /* add to new database */
+		    entry2 = AddCertToPermDB(handle, cert, entry->nickname,
+					     &entry->trust);
+		    
+		    CERT_DestroyCertificate(cert);
+		    if ( entry2 ) {
+			DestroyDBEntry((certDBEntry *)entry2);
+		    }
+		}
+		DestroyDBEntry((certDBEntry *)entry);
+	    }
+	}
+    } while ( (* updatedb->seq)(updatedb, &key, &data, R_NEXT) == 0 );
+
+    PORT_FreeArena(arena, PR_FALSE);
+    (* updatedb->close)(updatedb);
+    return(SECSuccess);
+}
+
+/*
+ * return true if a database key conflict exists
+ */
+PRBool
+SEC_CertDBKeyConflict(SECItem *derCert, CERTCertDBHandle *handle)
+{
+    SECStatus rv;
+    DBT tmpdata;
+    DBT namekey;
+    int ret;
+    SECItem keyitem;
+    PRArenaPool *arena = NULL;
+    SECItem derKey;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+
+    /* get the db key of the cert */
+    rv = CERT_KeyFromDERCert(arena, derCert, &derKey);
+    if ( rv != SECSuccess ) {
+        goto loser;
+    }
+
+    rv = EncodeDBCertKey(&derKey, arena, &keyitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    namekey.data = keyitem.data;
+    namekey.size = keyitem.len;
+    
+    /* lookup in the temporary database */
+    ret = certdb_Get(handle->tempCertDB, &namekey, &tmpdata, 0);
+
+    if ( ret == 0 ) {	/* found in temp database */
+	goto loser;
+    } else {		/* not found in temporary database */
+	ret = certdb_Get(handle->permCertDB, &namekey, &tmpdata, 0);
+	if ( ret == 0 ) {
+	    goto loser;
+	}
+    }
+
+    PORT_FreeArena(arena, PR_FALSE);
+    
+    return(PR_FALSE);
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(PR_TRUE);
+}
+
+#ifdef NOTDEF
+/*
+ * return true if a subject name conflict exists
+ * NOTE: caller must have already made sure that this exact cert
+ * doesn't exist in the DB
+ */
+PRBool
+SEC_CertSubjectConflict(SECItem *derCert, CERTCertDBHandle *handle)
+{
+    SECStatus rv;
+    DBT tmpdata;
+    DBT namekey;
+    int ret;
+    SECItem keyitem;
+    PRArenaPool *arena = NULL;
+    SECItem derName;
+    
+    derName.data = NULL;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+
+    /* get the subject name of the cert */
+    rv = CERT_NameFromDERCert(derCert, &derName);
+    if ( rv != SECSuccess ) {
+        goto loser;
+    }
+
+    rv = EncodeDBSubjectKey(&derName, arena, &keyitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    namekey.data = keyitem.data;
+    namekey.size = keyitem.len;
+    
+    /* lookup in the temporary database */
+    ret = certdb_Get(handle->tempCertDB, &namekey, &tmpdata, 0);
+
+    if ( ret == 0 ) {	/* found in temp database */
+	return(PR_TRUE);
+    } else {		/* not found in temporary database */
+	ret = certdb_Get(handle->permCertDB, &namekey, &tmpdata, 0);
+	if ( ret == 0 ) {
+	    return(PR_TRUE);
+	}
+    }
+
+    PORT_FreeArena(arena, PR_FALSE);
+    PORT_Free(derName.data);
+    
+    return(PR_FALSE);
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    if ( derName.data ) {
+	PORT_Free(derName.data);
+    }
+    
+    return(PR_TRUE);
+}
+#endif
+
+/*
+ * return true if a nickname conflict exists
+ * NOTE: caller must have already made sure that this exact cert
+ * doesn't exist in the DB
+ */
+PRBool
+SEC_CertNicknameConflict(char *nickname, SECItem *derSubject,
+			 CERTCertDBHandle *handle)
+{
+    PRBool rv;
+    certDBEntryNickname *entry;
+    
+    if ( nickname == NULL ) {
+	return(PR_FALSE);
+    }
+    
+    entry = ReadDBNicknameEntry(handle, nickname);
+
+    if ( entry == NULL ) {
+	/* no entry for this nickname, so no conflict */
+	return(PR_FALSE);
+    }
+
+    rv = PR_TRUE;
+    if ( SECITEM_CompareItem(derSubject, &entry->subjectName) == SECEqual ) {
+	/* if subject names are the same, then no conflict */
+	rv = PR_FALSE;
+    }
+
+    DestroyDBEntry((certDBEntry *)entry);
+    return(rv);
+}
+
+/*
+ * Open the certificate database and index databases.  Create them if
+ * they are not there or bad.
+ */
+SECStatus
+SEC_OpenPermCertDB(CERTCertDBHandle *handle, PRBool readOnly,
+		   CERTDBNameFunc namecb, void *cbarg)
+{
+    SECStatus rv;
+    int openflags;
+    certDBEntryVersion *versionEntry = NULL;
+    DB *updatedb = NULL;
+    char *tmpname;
+    char *certdbname;
+    PRBool updated = PR_FALSE;
+    PRBool forceUpdate = PR_FALSE;
+    
+    certdbname = (* namecb)(cbarg, CERT_DB_FILE_VERSION);
+    if ( certdbname == NULL ) {
+	return(SECFailure);
+    }
+    
+    if ( readOnly ) {
+	openflags = O_RDONLY;
+    } else {
+	openflags = O_RDWR;
+    }
+    
+    /*
+     * first open the permanent file based database.
+     */
+    handle->permCertDB = dbopen( certdbname, openflags, 0600, DB_HASH, 0 );
+
+    /* check for correct version number */
+    if ( handle->permCertDB ) {
+	versionEntry = ReadDBVersionEntry(handle);
+
+	if ( versionEntry == NULL ) {
+	    /* no version number */
+	    certdb_Close(handle->permCertDB);
+	    handle->permCertDB = 0;
+	} else if ( versionEntry->common.version != CERT_DB_FILE_VERSION ) {
+	    /* wrong version number, can't update in place */
+	    DestroyDBEntry((certDBEntry *)versionEntry);
+	    PORT_Free(certdbname);
+	    return(SECFailure);
+	}
+
+    }
+
+
+    /* if first open fails, try to create a new DB */
+    if ( handle->permCertDB == NULL ) {
+
+	/* don't create if readonly */
+	if ( readOnly ) {
+	    goto loser;
+	}
+	
+	handle->permCertDB = dbopen(certdbname,
+				    O_RDWR | O_CREAT | O_TRUNC,
+				    0600, DB_HASH, 0);
+
+	/* if create fails then we lose */
+	if ( handle->permCertDB == 0 ) {
+	    goto loser;
+	}
+
+	versionEntry = NewDBVersionEntry(0);
+	if ( versionEntry == NULL ) {
+	    goto loser;
+	}
+	
+	rv = WriteDBVersionEntry(handle, versionEntry);
+
+	DestroyDBEntry((certDBEntry *)versionEntry);
+
+	if ( rv != SECSuccess ) {
+	    goto loser;
+	}
+
+	/* try to upgrade old db here */
+	tmpname = (* namecb)(cbarg, 6);	/* get v6 db name */
+	if ( tmpname ) {
+	    updatedb = dbopen( tmpname, O_RDONLY, 0600, DB_HASH, 0 );
+	    PORT_Free(tmpname);
+	    if ( updatedb ) {
+		rv = UpdateV6DB(handle, updatedb);
+		if ( rv != SECSuccess ) {
+		    goto loser;
+		}
+		updated = PR_TRUE;
+	    } else { /* no v6 db, so try v5 db */
+		tmpname = (* namecb)(cbarg, 5);	/* get v5 db name */
+		if ( tmpname ) {
+		    updatedb = dbopen( tmpname, O_RDONLY, 0600, DB_HASH, 0 );
+		    PORT_Free(tmpname);
+		    if ( updatedb ) {
+			rv = UpdateV5DB(handle, updatedb);
+			if ( rv != SECSuccess ) {
+			    goto loser;
+			}
+			updated = PR_TRUE;
+		    } else { /* no v5 db, so try v4 db */
+			/* try to upgrade v4 db */
+			tmpname = (* namecb)(cbarg, 4);	/* get v4 db name */
+			if ( tmpname ) {
+			    updatedb = dbopen( tmpname, O_RDONLY, 0600,
+					      DB_HASH, 0 );
+			    PORT_Free(tmpname);
+			    if ( updatedb ) {
+				/* NES has v5 db's with v4 db names! */
+				if (isV4DB(updatedb)) {
+				    rv = UpdateV4DB(handle, updatedb);
+				} else {
+				    rv = UpdateV5DB(handle, updatedb);
+				}
+				if ( rv != SECSuccess ) {
+				    goto loser;
+				}
+				forceUpdate = PR_TRUE;
+				updated = PR_TRUE;
+			    }
+			}
+		    }
+		}
+	    }
+	}
+
+	/* initialize the database with our well known certificates
+	 * or in the case of update, just fall down to CERT_AddNewCerts()
+	 * below.
+	 * Note - if we are updating a really old database, then we try
+	 * to push all of the certs into it.
+	 */
+	if ( ( !updated ) || forceUpdate ) {
+	    rv = CERT_InitCertDB(handle);
+	    if ( rv != SECSuccess ) {
+		goto loser;
+	    }
+	}
+    }
+
+    rv = CERT_AddNewCerts(handle);
+
+    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(CERTCertificate *cert)
+{
+    SECStatus rv;
+    SECStatus ret;
+
+    ret = SECSuccess;
+    
+    rv = DeleteDBCertEntry(cert->dbhandle, &cert->certKey);
+    if ( rv != SECSuccess ) {
+	ret = SECFailure;
+    }
+    
+    if ( cert->nickname ) {
+	rv = DeleteDBNicknameEntry(cert->dbhandle, cert->nickname);
+	if ( rv != SECSuccess ) {
+	    ret = SECFailure;
+	}
+    }
+    
+    rv = RemovePermSubjectNode(cert);
+
+    return(ret);
+}
+
+/*
+ * Delete a certificate from the permanent database.
+ */
+SECStatus
+SEC_DeletePermCertificate(CERTCertificate *cert)
+{
+    SECStatus rv;
+    
+    if ( !cert->isperm ) {
+	return(SECSuccess);
+    }
+    CERT_LockDB(cert->dbhandle);
+    /* delete the records from the permanent database */
+    rv = DeletePermCert(cert);
+    
+    /* no longer permanent */
+    cert->isperm = PR_FALSE;
+
+    /* get rid of dbcert and stuff pointing to it */
+    DestroyDBEntry((certDBEntry *)cert->dbEntry);
+    cert->dbEntry = NULL;
+    cert->trust = NULL;
+
+    /* delete it from the temporary database too.  It will remain in
+     * memory until all references go away.
+     */
+    if (cert->slot) {
+    /* If it's owned by a PKCS #11 slot, don't deleted if from the temp DB just
+     * yet... rv inherited from DeletePermCert (as if anyone checks the return
+     * code from this function anyway. */
+	CERT_DestroyCertificateNoLocking(cert);
+	rv = SECSuccess;
+    } else {
+	rv = CERT_DeleteTempCertificate(cert);
+    }
+
+    CERT_UnlockDB(cert->dbhandle);
+    return(rv);
+}
+
+/*
+ * Lookup a certificate in the databases.
+ */
+certDBEntryCert *
+SEC_FindPermCertByKey(CERTCertDBHandle *handle, SECItem *key)
+{
+    return(ReadDBCertEntry(handle, key));
+}
+
+/*
+ * Lookup a certificate in the database by name
+ */
+certDBEntryCert *
+SEC_FindPermCertByName(CERTCertDBHandle *handle, SECItem *derSubject)
+{
+    certDBEntrySubject *subjectEntry;
+    certDBEntryCert *certEntry;
+    
+    subjectEntry = ReadDBSubjectEntry(handle, derSubject);
+    
+    if ( subjectEntry == NULL ) {
+	goto loser;
+    }
+
+    certEntry = ReadDBCertEntry(handle, &subjectEntry->certKeys[0]);
+    DestroyDBEntry((certDBEntry *)subjectEntry);
+    
+    return(certEntry);
+
+loser:
+    return(NULL);
+}
+
+/*
+ * Lookup a certificate in the database by nickname
+ */
+certDBEntryCert *
+SEC_FindPermCertByNickname(CERTCertDBHandle *handle, char *nickname)
+{
+    certDBEntryNickname *nicknameEntry;
+    certDBEntryCert *certEntry;
+    
+    nicknameEntry = ReadDBNicknameEntry(handle, nickname);
+    
+    if ( nicknameEntry == NULL ) {
+	goto loser;
+    }
+
+    certEntry = SEC_FindPermCertByName(handle, &nicknameEntry->subjectName);
+    DestroyDBEntry((certDBEntry *)nicknameEntry);
+    
+    return(certEntry);
+
+loser:
+    return(NULL);
+}
+
+/*
+ * Traverse all of the entries in the database of a particular type
+ * call the given function for each one.
+ */
+SECStatus
+SEC_TraverseDBEntries(CERTCertDBHandle *handle,
+		      certDBEntryType type,
+		      SECStatus (* callback)(SECItem *data, SECItem *key,
+					    certDBEntryType type, void *pdata),
+		      void *udata )
+{
+    DBT data;
+    DBT key;
+    SECStatus rv;
+    int ret;
+    SECItem dataitem;
+    SECItem keyitem;
+    unsigned char *buf;
+    unsigned char *keybuf;
+    
+    ret = certdb_Seq(handle->permCertDB, &key, &data, R_FIRST);
+
+    if ( ret ) {
+	return(SECFailure);
+    }
+    
+    do {
+	buf = (unsigned char *)data.data;
+	
+	if ( buf[1] == (unsigned char)type ) {
+	    dataitem.len = data.size;
+	    dataitem.data = buf;
+            dataitem.type = siBuffer;
+	    keyitem.len = key.size - SEC_DB_KEY_HEADER_LEN;
+	    keybuf = (unsigned char *)key.data;
+	    keyitem.data = &keybuf[SEC_DB_KEY_HEADER_LEN];
+            keyitem.type = siBuffer;
+	    
+	    rv = (* callback)(&dataitem, &keyitem, type, udata);
+	    if ( rv != SECSuccess ) {
+		return(rv);
+	    }
+	}
+    } while ( certdb_Seq(handle->permCertDB, &key, &data, R_NEXT) == 0 );
+
+    return(SECSuccess);
+}
+
+typedef struct {
+    PermCertCallback certfunc;
+    CERTCertDBHandle *handle;
+    void *data;
+} PermCertCallbackState;
+
+/*
+ * traversal callback to decode certs and call callers callback
+ */
+static SECStatus
+certcallback(SECItem *dbdata, SECItem *dbkey, certDBEntryType type, void *data)
+{
+    PermCertCallbackState *mystate;
+    SECStatus rv;
+    certDBEntryCert entry;
+    SECItem entryitem;
+    CERTCertificate *cert;
+    PRArenaPool *arena = NULL;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+    
+    mystate = (PermCertCallbackState *)data;
+    entry.common.version = (unsigned int)dbdata->data[0];
+    entry.common.type = (certDBEntryType)dbdata->data[1];
+    entry.common.flags = (unsigned int)dbdata->data[2];
+    entry.common.arena = arena;
+    
+    entryitem.len = dbdata->len - SEC_DB_ENTRY_HEADER_LEN;
+    entryitem.data = &dbdata->data[SEC_DB_ENTRY_HEADER_LEN];
+    
+    rv = DecodeDBCertEntry(&entry, &entryitem);
+    if (rv != SECSuccess ) {
+	goto loser;
+    }
+    entry.derCert.type = siBuffer;
+    
+    cert = CERT_DecodeDERCertificate(&entry.derCert, PR_FALSE,
+				    entry.nickname);
+    cert->dbEntry = &entry;
+    cert->trust = &entry.trust;
+    cert->dbhandle = mystate->handle;
+
+    if ( CERT_IsCACert(cert, NULL) ||
+         (( cert->trust->sslFlags & CERTDB_VALID_CA ) ||
+          ( cert->trust->emailFlags & CERTDB_VALID_CA ) ||
+          ( cert->trust->objectSigningFlags & CERTDB_VALID_CA)) ) {
+        cert->nsCertType |= EXT_KEY_USAGE_STATUS_RESPONDER;
+    }
+
+    rv = (* mystate->certfunc)(cert, dbkey, mystate->data);
+
+    /* arena destroyed by SEC_DestroyCert */
+    CERT_DestroyCertificateNoLocking(cert);
+
+    return(rv);
+
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    return(SECFailure);
+}
+
+/*
+ * Traverse all of the certificates in the permanent database and
+ * call the given function for each one; expect the caller to have lock.
+ */
+static SECStatus
+TraversePermCertsNoLocking(CERTCertDBHandle *handle,
+			   SECStatus (* certfunc)(CERTCertificate *cert,
+						  SECItem *k,
+						  void *pdata),
+			   void *udata )
+{
+    SECStatus rv;
+    PermCertCallbackState mystate;
+
+    mystate.certfunc = certfunc;
+    mystate.handle = handle;
+    mystate.data = udata;
+    rv = SEC_TraverseDBEntries(handle, certDBEntryTypeCert, certcallback,
+			       (void *)&mystate);
+    
+    return(rv);
+}
+
+/*
+ * Traverse all of the certificates in the permanent database and
+ * call the given function for each one.
+ */
+SECStatus
+SEC_TraversePermCerts(CERTCertDBHandle *handle,
+		      SECStatus (* certfunc)(CERTCertificate *cert, SECItem *k,
+					    void *pdata),
+		      void *udata )
+{
+    SECStatus rv;
+
+    CERT_LockDB(handle);
+    rv = TraversePermCertsNoLocking(handle, certfunc, udata);
+    CERT_UnlockDB(handle);
+    
+    return(rv);
+}
+
+
+
+/*
+ * Close the database
+ */
+void
+__CERT_ClosePermCertDB(CERTCertDBHandle *handle)
+{
+    if ( handle ) {
+	if ( handle->permCertDB ) {
+	    if ( handle->statusConfig ) {
+		PORT_Assert(handle->statusConfig->statusDestroy != NULL);
+		(void) (* handle->statusConfig->statusDestroy)(handle->statusConfig);
+		handle->statusConfig = NULL; /* Destroy frees the structure */
+		PORT_Assert(handle->statusConfig == NULL);
+	    }
+	    certdb_Close( handle->permCertDB );
+	    handle->permCertDB = 0;
+	}
+    }
+    return;
+}
+
+void
+CERT_ClosePermCertDB(CERTCertDBHandle *handle)
+{
+    __CERT_ClosePermCertDB(handle);
+}
+
+/*
+ * Get the trust attributes from a certificate
+ */
+SECStatus
+CERT_GetCertTrust(CERTCertificate *cert, CERTCertTrust *trust)
+{
+    SECStatus rv;
+    
+    CERT_LockCertTrust(cert);
+    
+    if ( cert->trust == NULL ) {
+	rv = SECFailure;
+    } else {
+	*trust = *cert->trust;
+	rv = SECSuccess;
+    }
+    
+    CERT_UnlockCertTrust(cert);
+    return(rv);
+}
+
+static char *
+cert_parseNickname(char *nickname)
+{
+	char *cp;
+
+	for (cp=nickname; *cp && *cp != ':'; cp++);
+
+	if (*cp == ':') return cp+1;
+	return nickname;
+}
+
+/*
+ * Change the trust attributes of a certificate and make them permanent
+ * in the database.
+ */
+SECStatus
+CERT_ChangeCertTrust(CERTCertDBHandle *handle, CERTCertificate *cert,
+		    CERTCertTrust *trust)
+{
+    certDBEntryCert *entry;
+    int rv;
+    SECStatus ret;
+    
+    CERT_LockDB(handle);
+    CERT_LockCertTrust(cert);
+    /* only set the trust on permanent certs */
+    if ( cert->trust == NULL ) {
+	ret = SECFailure;
+	goto done;
+    }
+
+    *cert->trust = *trust;
+    if ( cert->dbEntry == NULL ) {
+	ret = SECSuccess; /* not in permanent database */
+	if ((cert->slot)  && PK11_IsReadOnly(cert->slot)) {
+	    char *nickname = cert_parseNickname(cert->nickname);
+	    ret = CERT_AddTempCertToPerm(cert, nickname, trust);
+	} 
+	goto done;
+    }
+    
+    entry = cert->dbEntry;
+    entry->trust = *trust;
+    
+    rv = WriteDBCertEntry(handle, entry);
+    if ( rv ) {
+	ret = SECFailure;
+	goto done;
+    }
+
+    ret = SECSuccess;
+    
+done:
+    CERT_UnlockCertTrust(cert);
+    CERT_UnlockDB(handle);
+    return(ret);
+}
+
+
+SECStatus
+CERT_AddTempCertToPerm(CERTCertificate *cert, char *nickname,
+		       CERTCertTrust *trust)
+{
+    char *oldnn;
+    certDBEntryCert *entry;
+    SECStatus rv;
+    PRBool conflict;
+    SECStatus ret;
+
+    PORT_Assert(cert->dbhandle);
+
+    CERT_LockDB(cert->dbhandle);
+    
+    PORT_Assert(cert->istemp);
+    PORT_Assert(!cert->isperm);
+    PORT_Assert(!cert->dbEntry);
+
+    /* don't add a conflicting nickname */
+    conflict = SEC_CertNicknameConflict(nickname, &cert->derSubject,
+					cert->dbhandle);
+    if ( conflict ) {
+	ret = SECFailure;
+	goto done;
+    }
+    
+    /* save old nickname so that we can delete it */
+    oldnn = cert->nickname;
+
+    entry = AddCertToPermDB(cert->dbhandle, cert, nickname, trust);
+    
+    if ( entry == NULL ) {
+	ret = SECFailure;
+	goto done;
+    }
+    
+    cert->nickname = (entry->nickname) ? PORT_ArenaStrdup(cert->arena,entry->nickname) : NULL;
+    cert->trust = &entry->trust;
+    cert->isperm = PR_TRUE;
+    cert->dbEntry = entry;
+
+    if ( nickname && oldnn && ( PORT_Strcmp(nickname, oldnn) != 0 ) ) {
+	/* only delete the old one if they are not the same */
+	/* delete old nickname from temp database */
+	rv = SEC_DeleteTempNickname(cert->dbhandle, oldnn);
+	if ( rv != SECSuccess ) {
+	    /* do we care?? */
+	}
+    }
+    /* add new nickname to temp database */
+    if ( cert->nickname ) {
+	rv = SEC_AddTempNickname(cert->dbhandle, cert->nickname,
+				 &cert->derSubject);
+	if ( rv != SECSuccess ) {
+	    ret = SECFailure;
+	    goto done;
+	}
+    }
+    
+    ret = SECSuccess;
+done:
+    CERT_UnlockDB(cert->dbhandle);
+    return(ret);
+}
+
+/*
+ * Open the certificate database and index databases.  Create them if
+ * they are not there or bad.
+ */
+SECStatus
+CERT_OpenCertDB(CERTCertDBHandle *handle, PRBool readOnly,
+		CERTDBNameFunc namecb, void *cbarg)
+{
+    int rv;
+#define DBM_DEFAULT 0
+    static const HASHINFO hashInfo = {
+        DBM_DEFAULT,    /* bucket size */
+        DBM_DEFAULT,    /* fill factor */
+        DBM_DEFAULT,    /* number of elements */
+        256 * 1024, 	/* bytes to cache */
+        DBM_DEFAULT,    /* hash function */
+        DBM_DEFAULT     /* byte order */
+    };
+
+    certdb_InitDBLock();
+    
+    handle->dbMon = PZ_NewMonitor(nssILockCertDB);
+    PORT_Assert(handle->dbMon != NULL);
+
+    handle->spkDigestInfo = NULL;
+    handle->statusConfig = NULL;
+
+    /*
+     * Open the memory resident decoded cert database.
+     */
+    handle->tempCertDB = dbopen(0, O_RDWR | O_CREAT, 0600, DB_HASH, &hashInfo);
+    if ( !handle->tempCertDB ) {
+	goto loser;
+    }
+
+    rv = SEC_OpenPermCertDB(handle, readOnly, namecb, cbarg);
+    if ( rv ) {
+	goto loser;
+    }
+
+    return (SECSuccess);
+    
+loser:
+
+    PORT_SetError(SEC_ERROR_BAD_DATABASE);
+    
+    if ( handle->tempCertDB ) {
+	certdb_Close(handle->tempCertDB);
+	handle->tempCertDB = 0;
+    }
+
+    return(SECFailure);
+}
+
+static char *
+certDBFilenameCallback(void *arg, int dbVersion)
+{
+    return(PORT_Strdup((char *)arg));
+}
+
+SECStatus
+CERT_OpenCertDBFilename(CERTCertDBHandle *handle, char *certdbname,
+			PRBool readOnly)
+{
+    return(CERT_OpenCertDB(handle, readOnly, certDBFilenameCallback,
+			   (void *)certdbname));
+}
+
+/*
+ * Add a nickname to the temp database
+ */
+SECStatus
+SEC_AddTempNickname(CERTCertDBHandle *handle, char *nickname,
+		    SECItem *subjectName)
+{
+    DBT namekey;
+    int ret;
+    SECItem nameitem;
+    SECStatus rv;
+    DBT keydata;
+    PRArenaPool *arena = NULL;
+    SECItem tmpitem;
+    
+    PORT_Assert(nickname != NULL);
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    rv = EncodeDBNicknameKey(nickname, arena, &nameitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+
+    namekey.data = nameitem.data;
+    namekey.size = nameitem.len;
+
+    /* see if an entry already exists */
+    ret = certdb_Get(handle->tempCertDB, &namekey, &keydata, 0);
+
+    if ( ret == 0 ) {
+	/* found in temp database */
+	tmpitem.data = (unsigned char*)keydata.data;
+	tmpitem.len = keydata.size;
+
+	if ( SECITEM_CompareItem(subjectName, &tmpitem) == SECEqual ) {
+	    /* same subject name */
+	    goto done;
+	} else {
+	    /* different subject name is an error */
+	    goto loser;
+	}
+    }
+    
+    keydata.data = subjectName->data;
+    keydata.size = subjectName->len;
+    
+    /* put into temp byname index */
+    ret = certdb_Put(handle->tempCertDB, &namekey, &keydata, R_NOOVERWRITE);
+
+    if ( ret ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+
+done:
+    PORT_FreeArena(arena, PR_FALSE);
+    return(SECSuccess);
+
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    return(SECFailure);
+}
+
+SECStatus
+SEC_DeleteTempNickname(CERTCertDBHandle *handle, char *nickname)
+{
+    DBT namekey;
+    SECStatus rv;
+    PRArenaPool *arena = NULL;
+    SECItem nameitem;
+    int ret;
+    
+    PORT_Assert(nickname != NULL);
+    if ( nickname == NULL ) {
+	return(SECSuccess);
+    }
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    /* format a database key based on the nickname */
+    if ( nickname ) {
+	rv = EncodeDBNicknameKey(nickname, arena, &nameitem);
+	if ( rv != SECSuccess ) {
+	    goto loser;
+	}
+
+	namekey.data = nameitem.data;
+	namekey.size = nameitem.len;
+
+	ret = certdb_Del(handle->tempCertDB, &namekey, 0);
+	if ( ret ) {
+	    goto loser;
+	}
+    }
+
+    PORT_FreeArena(arena, PR_FALSE);
+    
+    return(SECSuccess);
+
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    return(SECFailure);
+}
+
+/*
+ * Decode a certificate and enter it into the temporary certificate database.
+ * Deal with nicknames correctly
+ *
+ * nickname is only used if isperm == PR_TRUE
+ *
+ * This is the private entry point, and locking is optional
+ */
+static CERTCertificate *
+NewTempCertificate(CERTCertDBHandle *handle, SECItem *derCert, char *nickname,
+		   PRBool isperm, PRBool copyDER, PRBool lockdb)
+
+{
+    DBT key;
+    DBT data;
+    int status;
+    CERTCertificate *cert = NULL;
+    PRBool promoteError = PR_TRUE;
+    PRArenaPool *arena = NULL;
+    SECItem keyitem;
+    SECStatus rv;
+
+    if ( lockdb ) {
+	CERT_LockDB(handle);
+    }
+
+    if ( isperm == PR_FALSE ) {
+	cert = CERT_FindCertByDERCertNoLocking(handle, derCert);
+	if ( cert ) {
+	    goto winner;
+	}
+
+	nickname = NULL;
+    }
+
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+    
+    cert = CERT_DecodeDERCertificate(derCert, copyDER, nickname );
+    
+    if ( cert == NULL ) {
+	/* We want to save the decoding error here */
+	promoteError = PR_FALSE;
+	goto loser;
+    }
+
+    cert->dbhandle = handle;
+
+    /* only save pointer to cert in database */
+    data.data = &cert;
+    data.size = sizeof(cert);
+
+    /* if this is a perm cert, then it is already in the subject db */
+    if ( isperm == PR_FALSE ) {
+	/* enter into the subject index */
+	rv = AddTempCertToSubjectList(cert);
+	if ( rv != SECSuccess ) {
+	    goto loser;
+	}
+	/*
+	 * Since it's not a perm cert, add it to the key hash lookup; if it
+	 * is permanent it will either already be there or will get put there
+	 * later along with the rest of the perm certs.  A failure of the
+	 * addition does not seem to warrant failing this whole function,
+	 * so we intentionally ignore the returned status.
+	 */
+	(void) AddCertToSPKDigestTable(handle, cert);
+    } else {
+	cert->subjectList = FindSubjectList(cert->dbhandle, &cert->derSubject,
+					    PR_FALSE);
+    }
+    
+    rv = EncodeDBCertKey(&cert->certKey, arena, &keyitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    key.data = keyitem.data;
+    key.size = keyitem.len;
+    
+    /* enter into main db */
+    status = certdb_Put(handle->tempCertDB, &key, &data, R_NOOVERWRITE);
+    if ( status ) {
+	goto loser;
+    }
+
+    if ( cert->nickname ) {
+	status = SEC_AddTempNickname(handle, cert->nickname,
+				     &cert->derSubject);
+	if ( status ) {
+	    promoteError = PR_FALSE;
+	    goto loser;
+	}
+    }
+
+    cert->isperm = isperm;
+    cert->istemp = PR_TRUE;
+    
+    PORT_FreeArena(arena, PR_FALSE);
+
+winner:
+
+    if ( lockdb ) {
+	CERT_UnlockDB(handle);
+    }
+
+    return(cert);
+
+loser:
+    if ( cert ) {
+	CERT_DestroyCertificateNoLocking(cert);
+    }
+    
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+
+    if ( promoteError ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+    }
+
+    if ( lockdb ) {
+	CERT_UnlockDB(handle);
+    }
+
+    return(0);
+}
+
+/*
+ * Decode a certificate and enter it into the temporary certificate database.
+ * Deal with nicknames correctly
+ *
+ * nickname is only used if isperm == PR_TRUE
+ *
+ * This is the public entry point and does locking.
+ */
+CERTCertificate *
+__CERT_NewTempCertificate(CERTCertDBHandle *handle, SECItem *derCert,
+			char *nickname, PRBool isperm, PRBool copyDER)
+{
+    return( NewTempCertificate(handle, derCert, nickname, isperm, copyDER,
+			       PR_TRUE) );
+}
+
+CERTCertificate *
+CERT_NewTempCertificate(CERTCertDBHandle *handle, SECItem *derCert,
+			char *nickname, PRBool isperm, PRBool copyDER)
+{
+    return( __CERT_NewTempCertificate(handle, derCert, nickname,
+                                      isperm, copyDER) );
+}
+
+/*
+ * Decode a permanent certificate and enter it into the temporary certificate
+ * database.
+ */
+static CERTCertificate *
+SEC_AddPermCertToTemp(CERTCertDBHandle *handle, certDBEntryCert *entry)
+{
+    CERTCertificate *cert;
+
+    /* we already hold the lock */
+    cert = NewTempCertificate(handle, &entry->derCert, entry->nickname,
+			      PR_TRUE, PR_TRUE, PR_FALSE);
+    if ( !cert ) {
+	return(0);
+    }
+
+    cert->dbEntry = entry;
+
+    cert->trust = &entry->trust;
+    
+    return(cert);
+}
+
+SECStatus
+CERT_DeleteTempCertificate(CERTCertificate *cert)
+{
+    SECStatus rv;
+    DBT nameKey;
+    CERTCertDBHandle *handle;
+    SECItem keyitem;
+    PRArenaPool *arena;
+    int ret;
+    
+    handle = cert->dbhandle;
+
+    if ( !cert->istemp ) {
+	return(SECSuccess);
+    }
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+   
+    if (cert->slot) {
+	PK11_FreeSlot(cert->slot);
+	cert->slot = NULL;
+	cert->pkcs11ID = CK_INVALID_KEY;
+    }
+    
+    /* delete from subject list (also takes care of nickname) */
+    rv = RemoveTempCertFromSubjectList(cert);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    if ( !cert->isperm ) {
+	/*
+	 * Remove the cert from the subject public key digest table,
+	 * though we do not care if the removal fails (perhaps meaning
+	 * the cert wasn't even there).
+	 */
+	(void) RemoveCertFromSPKDigestTable(handle, cert);
+    }
+
+    rv = EncodeDBCertKey(&cert->certKey, arena, &keyitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    nameKey.data = keyitem.data;
+    nameKey.size = keyitem.len;
+    /* delete the cert */
+    ret = certdb_Del(handle->tempCertDB, &nameKey, 0);
+    if ( ret ) {
+	goto loser;
+    }
+
+    cert->istemp = PR_FALSE;
+
+    PORT_FreeArena(arena, PR_FALSE);
+    return(SECSuccess);
+
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+
+    return(SECFailure);
+}
+
+/*
+ * Lookup a certificate in the databases.
+ */
+static CERTCertificate *
+FindCertByKey(CERTCertDBHandle *handle, SECItem *certKey, PRBool lockdb)
+{
+    DBT tmpdata;
+    int ret;
+    SECItem keyitem;
+    DBT key;
+    SECStatus rv;
+    CERTCertificate *cert = NULL;
+    PRArenaPool *arena = NULL;
+    certDBEntryCert *entry;
+    PRBool locked = PR_FALSE;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+    
+    rv = EncodeDBCertKey(certKey, arena, &keyitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    key.data = keyitem.data;
+    key.size = keyitem.len;
+    
+    if ( lockdb ) {
+	locked = PR_TRUE;
+	CERT_LockDB(handle);
+    }
+	
+    /* lookup in the temporary database */
+    ret = certdb_Get( handle->tempCertDB, &key, &tmpdata, 0 );
+
+    /* error accessing the database */
+    if ( ret < 0 ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+
+    if ( ret == 0 ) {	/* found in temp database */
+	if ( tmpdata.size != sizeof(CERTCertificate *) ) {
+	    PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	    goto loser;
+	}
+	
+	PORT_Memcpy(&cert, tmpdata.data, tmpdata.size);
+	CERT_LockCertRefCount(cert);
+	cert->referenceCount++;
+	CERT_UnlockCertRefCount(cert);
+    }
+    if ( ret != 0 ) {
+	/* not found in temporary database */
+
+	/* find in perm database */
+	entry = SEC_FindPermCertByKey(handle, certKey);
+	
+	if ( entry == NULL ) {
+	    goto loser;
+	}
+    
+	cert = SEC_AddPermCertToTemp(handle, entry);
+    }
+
+loser:
+    if ( locked ) {
+	CERT_UnlockDB(handle);
+    }
+
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(cert);
+}
+
+/*
+ * Lookup a certificate in the databases, with locking
+ */
+CERTCertificate *
+CERT_FindCertByKey(CERTCertDBHandle *handle, SECItem *certKey)
+{
+    return(FindCertByKey(handle, certKey, PR_TRUE));
+}
+
+/*
+ * Lookup a certificate in the databases without locking
+ */
+CERTCertificate *
+CERT_FindCertByKeyNoLocking(CERTCertDBHandle *handle, SECItem *certKey)
+{
+    return(FindCertByKey(handle, certKey, PR_FALSE));
+}
+
+/*
+ * Generate a key from an issuerAndSerialNumber, and find the
+ * associated cert in the database.
+ */
+CERTCertificate *
+CERT_FindCertByIssuerAndSN(CERTCertDBHandle *handle, CERTIssuerAndSN *issuerAndSN)
+{
+    SECItem certKey;
+    CERTCertificate *cert;
+    
+    certKey.len = issuerAndSN->serialNumber.len + issuerAndSN->derIssuer.len;
+    certKey.data = (unsigned char*)PORT_Alloc(certKey.len);
+    
+    if ( certKey.data == NULL ) {
+	return(0);
+    }
+
+    /* copy the serialNumber */
+    PORT_Memcpy(certKey.data, issuerAndSN->serialNumber.data,
+	      issuerAndSN->serialNumber.len);
+
+    /* copy the issuer */
+    PORT_Memcpy( &certKey.data[issuerAndSN->serialNumber.len],
+	      issuerAndSN->derIssuer.data, issuerAndSN->derIssuer.len);
+
+    cert = CERT_FindCertByKey(handle, &certKey);
+    
+    PORT_Free(certKey.data);
+    
+    return(cert);
+}
+
+/*
+ * Lookup a certificate in the database by name
+ */
+CERTCertificate *
+CERT_FindCertByName(CERTCertDBHandle *handle, SECItem *name)
+{
+    CERTCertificate *cert = NULL;
+    CERTSubjectList *subjectList;
+    
+    CERT_LockDB(handle);
+
+    subjectList = FindSubjectList(handle, name, PR_FALSE);
+
+    if ( subjectList ) {
+	PORT_Assert(subjectList->head);
+	cert = CERT_FindCertByKeyNoLocking(handle,
+					   &subjectList->head->certKey);
+    }
+	
+    CERT_UnlockDB(handle);
+
+    return(cert);
+}
+
+/*
+ * Lookup a certificate in the database by name and key ID
+ */
+CERTCertificate *
+CERT_FindCertByKeyID(CERTCertDBHandle *handle, SECItem *name, SECItem *keyID)
+{
+    CERTCertificate *cert = NULL;
+    CERTSubjectList *subjectList;
+    CERTSubjectNode *node;
+
+    CERT_LockDB(handle);
+
+    /* find the list of certs for the given subject */
+    subjectList = FindSubjectList(handle, name, PR_FALSE);
+
+    if ( subjectList ) {
+	PORT_Assert(subjectList->head);
+	node = subjectList->head;
+
+	/* walk through the certs until we find one with a matching key ID */
+	while ( node ) {
+	    if ( SECITEM_CompareItem(keyID, &node->keyID) == SECEqual ) {
+		cert = CERT_FindCertByKeyNoLocking(handle, &node->certKey);
+		break;
+	    }
+	    node = node->next;
+	}
+    }
+
+    CERT_UnlockDB(handle);
+
+    return(cert);
+}
+
+/*
+ * look up a cert by its nickname string
+ */
+CERTCertificate *
+CERT_FindCertByNickname(CERTCertDBHandle *handle, char *nickname)
+{
+    DBT tmpdata;
+    DBT namekey;
+    CERTCertificate *cert;
+    SECStatus rv;
+    int ret;
+    SECItem keyitem;
+    PRArenaPool *arena = NULL;
+    certDBEntryCert *entry;
+    
+    PORT_Assert(nickname != NULL);
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+
+    rv = EncodeDBNicknameKey(nickname, arena, &keyitem);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    namekey.data = keyitem.data;
+    namekey.size = keyitem.len;
+    
+    /* lookup in the temporary database */
+    ret = certdb_Get(handle->tempCertDB, &namekey, &tmpdata, 0);
+
+    /* error accessing the database */
+    if ( ret < 0 ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+
+    if ( ret == 0 ) {	/* found in temp database */
+	SECItem nameitem;
+	
+	nameitem.len = tmpdata.size;
+	nameitem.data = (unsigned char *)PORT_Alloc(tmpdata.size);
+	if ( nameitem.data == NULL ) {
+	    goto loser;
+	}
+	PORT_Memcpy(nameitem.data, tmpdata.data, nameitem.len);
+	cert = CERT_FindCertByName(handle, &nameitem);
+	PORT_Free(nameitem.data);
+    } else {		/* not found in temporary database */
+
+	CERT_LockDB(handle);
+	
+	entry = SEC_FindPermCertByNickname(handle, nickname);
+	
+	if ( entry == NULL ) {
+	    CERT_UnlockDB(handle);
+	    goto loser;
+	}
+    
+	cert = SEC_AddPermCertToTemp(handle, entry);
+	CERT_UnlockDB(handle);
+    }
+
+    PORT_FreeArena(arena, PR_FALSE);
+
+    return(cert);
+
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(0);
+}
+
+/*
+ * look for the given DER certificate in the database
+ */
+CERTCertificate *
+CERT_FindCertByDERCert(CERTCertDBHandle *handle, SECItem *derCert)
+{
+    PRArenaPool *arena;
+    SECItem certKey;
+    SECStatus rv;
+    CERTCertificate *cert = NULL;
+    
+    /* create a scratch arena */
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	return(NULL);
+    }
+    
+    /* extract the database key from the cert */
+    rv = CERT_KeyFromDERCert(arena, derCert, &certKey);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+
+    /* find the certificate */
+    cert = CERT_FindCertByKey(handle, &certKey);
+    
+loser:
+    PORT_FreeArena(arena, PR_FALSE);
+    return(cert);
+}
+
+/*
+ * look for the given DER certificate in the database
+ */
+CERTCertificate *
+CERT_FindCertByDERCertNoLocking(CERTCertDBHandle *handle, SECItem *derCert)
+{
+    PRArenaPool *arena;
+    SECItem certKey;
+    SECStatus rv;
+    CERTCertificate *cert = NULL;
+    
+    /* create a scratch arena */
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	return(NULL);
+    }
+    
+    /* extract the database key from the cert */
+    rv = CERT_KeyFromDERCert(arena, derCert, &certKey);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+
+    /* find the certificate */
+    cert = CERT_FindCertByKeyNoLocking(handle, &certKey);
+    
+loser:
+    PORT_FreeArena(arena, PR_FALSE);
+    return(cert);
+}
+
+/*
+ * The following is bunch of types and code to allow looking up a certificate
+ * by a hash of its subject public key.  Because the words "hash" and "key"
+ * are overloaded and thus terribly confusing, I tried to disambiguate things.
+ * - Where I could, I used "digest" instead of "hash" when referring to
+ *   hashing of the subject public key.  The PLHashTable interfaces and
+ *   our own HASH_Foo interfaces had to be left as is, obviously.  The latter
+ *   should be thought of as "digest" in this case.
+ * - There are three keys in use here -- the subject public key, the key
+ *   used to do a lookup in the PLHashTable, and the key used to do a lookup
+ *   in the cert database.  As the latter is a fairly pervasive interface,
+ *   I left it alone.  The other two uses I changed to "spk" or "SPK" when
+ *   referring to the subject public key, and "index" when referring to the
+ *   key into the PLHashTable.
+ */
+
+typedef struct SPKDigestInfoStr {
+    PLHashTable *table;
+    PRBool permPopulated;
+} SPKDigestInfo;
+
+/*
+ * Since the key hash information is "hidden" (in a void pointer in the handle)
+ * these macros with the appropriate casts make it easy to get at the parts.
+ */
+#define SPK_DIGEST_TABLE(handle)	\
+	(((SPKDigestInfo *)(handle->spkDigestInfo))->table)
+
+/*
+** Hash allocator ops for the SPKDigest hash table.  The rules are:
+**   + The index and value fields are "owned" by the hash table, and are
+**     freed when the table entry is deleted.
+**   + Replacing a value in the table is not allowed, since the caller can't
+**     tell whether the index field was used or not, resulting in a memory
+**     leak.  (This is a bug in the PL_Hash routines.
+*/
+static void * PR_CALLBACK
+spkAllocTable(void *pool, PRSize size)
+{
+#if defined(XP_MAC)
+#pragma unused (pool)
+#endif
+
+    return PR_MALLOC(size);
+}
+
+static void PR_CALLBACK
+spkFreeTable(void *pool, void *item)
+{
+#if defined(XP_MAC)
+#pragma unused (pool)
+#endif
+
+    PR_Free(item);
+}
+
+/* NOTE: the key argument here appears to be useless, since the RawAdd
+ * routine in PL_Hash just uses the original anyway.
+ */
+static PLHashEntry * PR_CALLBACK
+spkAllocEntry(void *pool, const void *key)
+{
+#if defined(XP_MAC)
+#pragma unused (pool,key)
+#endif
+
+    return PR_NEW(PLHashEntry);
+}
+
+static void PR_CALLBACK
+spkFreeEntry(void *pool, PLHashEntry *he, PRUintn flag)
+{
+#if defined(XP_MAC)
+#pragma unused (pool)
+#endif
+
+    SECItem *value = (SECItem *)he->value;
+
+    /* The flag should always be to free the whole entry.  Otherwise the
+     * index field gets leaked because the caller can't tell whether
+     * the "new" value (which is the same as the old) was used or not.
+     */
+    PORT_Assert(flag == HT_FREE_ENTRY);
+
+    /* We always free the value */
+    SECITEM_FreeItem(value, PR_TRUE);
+    
+    if (flag == HT_FREE_ENTRY)
+    {
+        /* Comes from BTOA, is this the right free call? */
+        PORT_Free((char *)he->key);
+        PR_Free(he);
+    }
+}
+
+static PLHashAllocOps spkHashAllocOps = {
+    spkAllocTable, spkFreeTable,
+    spkAllocEntry, spkFreeEntry
+};
+
+
+/*
+ * Create the key hash lookup table.  Note that the table, and the
+ * structure which holds it and a little more information, is never freed.
+ * This is because the temporary database is never actually closed out,
+ * so there is no safe/obvious place to free the whole thing.
+ *
+ * The database must be locked already.
+ */
+static SECStatus
+InitDBspkDigestInfo(CERTCertDBHandle *handle)
+{
+    SPKDigestInfo *spkDigestInfo;
+    PLHashTable *table;
+
+    PORT_Assert(handle != NULL);
+    PORT_Assert(handle->spkDigestInfo == NULL);
+
+    spkDigestInfo = PORT_ZAlloc(sizeof(SPKDigestInfo));
+    if ( spkDigestInfo == NULL ) {
+	return(SECFailure);
+    }
+
+    table = PL_NewHashTable(128, PL_HashString, PL_CompareStrings,
+			    (PLHashComparator) SECITEM_ItemsAreEqual,
+			    &spkHashAllocOps, NULL);
+    if ( table == NULL ) {
+	PORT_Free(spkDigestInfo);
+	return(SECFailure);
+    }
+
+    spkDigestInfo->table = table;
+    handle->spkDigestInfo = spkDigestInfo;
+    return(SECSuccess);
+}
+
+static const SECHashObject *
+OidTagToRawDigestObject(SECOidTag digestAlg)
+{
+    const SECHashObject *rawDigestObject;
+
+    switch (digestAlg) {
+      case SEC_OID_MD2:
+	rawDigestObject = &SECRawHashObjects[HASH_AlgMD2];
+	break;
+      case SEC_OID_MD5:
+	rawDigestObject = &SECRawHashObjects[HASH_AlgMD5];
+	break;
+      case SEC_OID_SHA1:
+	rawDigestObject = &SECRawHashObjects[HASH_AlgSHA1];
+	break;
+      default:
+	PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+	rawDigestObject = NULL;
+	break;
+    }
+    return(rawDigestObject);
+}
+
+/*
+ * Digest the cert's subject public key using the specified algorithm.
+ * The necessary storage for the digest data is allocated.  If "fill" is
+ * non-null, the data is put there, otherwise a SECItem is allocated.
+ * Allocation from "arena" if it is non-null, heap otherwise.  Any problem
+ * results in a NULL being returned (and an appropriate error set).
+ */ 
+SECItem *
+CERT_SPKDigestValueForCert(PRArenaPool *arena, CERTCertificate *cert,
+			   SECOidTag digestAlg, SECItem *fill)
+{
+    const SECHashObject *digestObject;
+    void *digestContext;
+    SECItem *result = NULL;
+    void *mark = NULL;
+    SECItem spk;
+
+    if ( arena != NULL ) {
+	mark = PORT_ArenaMark(arena);
+    }
+
+    /*
+     * This can end up being called before PKCS #11 is initialized,
+     * so we have to use the raw digest functions.
+     */
+    digestObject = OidTagToRawDigestObject(digestAlg);
+    if ( digestObject == NULL ) {
+	goto loser;
+    }
+
+    result = SECITEM_AllocItem(arena, fill, digestObject->length);
+    if ( result == NULL ) {
+	goto loser;
+    }
+
+    /*
+     * Copy just the length and data pointer (nothing needs to be freed)
+     * of the subject public key so we can convert the length from bits
+     * to bytes, which is what the digest function expects.
+     */
+    spk = cert->subjectPublicKeyInfo.subjectPublicKey;
+    DER_ConvertBitString(&spk);
+
+    /*
+     * Now digest the value, using the specified algorithm.
+     */
+    digestContext = digestObject->create();
+    if ( digestContext == NULL ) {
+	goto loser;
+    }
+    digestObject->begin(digestContext);
+    digestObject->update(digestContext, spk.data, spk.len);
+    digestObject->end(digestContext, result->data, &(result->len), result->len);
+    digestObject->destroy(digestContext, PR_TRUE);
+
+    if ( arena != NULL ) {
+	PORT_ArenaUnmark(arena, mark);
+    }
+    return(result);
+
+loser:
+    if ( arena != NULL ) {
+	PORT_ArenaRelease(arena, mark);
+    } else {
+	if ( result != NULL ) {
+	    SECITEM_FreeItem(result, (fill == NULL) ? PR_TRUE : PR_FALSE);
+	}
+    }
+    return(NULL);
+}
+
+/*
+ * Return the index for the spk digest lookup table for "spkDigest".
+ *
+ * Caller is responsible for freeing the returned string.
+ */
+static char *
+spkDigestIndexFromDigest(SECItem *spkDigest)
+{
+    return BTOA_ConvertItemToAscii(spkDigest);
+}
+
+/*
+ * Return the index for the spk digest lookup table for this certificate,
+ * based on the specified digest algorithm.
+ *
+ * Caller is responsible for freeing the returned string.
+ */
+static char *
+spkDigestIndexFromCert(CERTCertificate *cert, SECOidTag digestAlg)
+{
+    SECItem *spkDigest;
+    char *index;
+
+    spkDigest = CERT_SPKDigestValueForCert(NULL, cert, digestAlg, NULL);
+    if ( spkDigest == NULL )
+	return(NULL);
+
+    index = spkDigestIndexFromDigest(spkDigest);
+
+    SECITEM_FreeItem(spkDigest, PR_TRUE);
+
+    return(index);
+}
+
+/*
+ * Remove the spk digest for the given cert from the spk digest table,
+ * based on the given digest algorithm.
+ *
+ * The database must be locked already.
+ */
+static SECStatus
+RemoveCertFromSPKDigestTableForAlg(CERTCertDBHandle *handle,
+				   CERTCertificate *cert, SECOidTag digestAlg)
+{
+    SECStatus rv = SECSuccess;
+    char *index = NULL;
+    PLHashTable *table;
+
+    /* Expect to only be called if there is a table to work with. */
+    PORT_Assert(handle->spkDigestInfo != NULL);
+
+    table = SPK_DIGEST_TABLE(handle);
+    PORT_Assert(table != NULL);
+
+    index = spkDigestIndexFromCert(cert, digestAlg);
+    if ( index == NULL ) {
+	rv = SECFailure;
+	goto done;
+    }
+
+    if ( PL_HashTableRemove(table, index) != PR_TRUE ) {
+	/* not found means nothing to remove, which is fine */
+    }
+
+done:
+    if ( index != NULL ) {
+	PORT_Free(index);
+    }
+    return(rv);
+}
+
+/*
+ * Remove the spk digests for the given cert from the spk digest table,
+ * for all known digest algorithms.
+ *
+ * The database must be locked already.
+ */
+static SECStatus
+RemoveCertFromSPKDigestTable(CERTCertDBHandle *handle, CERTCertificate *cert)
+{
+    /*
+     * If no certs have been added yet, then nothing to do.
+     */
+    if ( handle->spkDigestInfo == NULL ) {
+	return(SECSuccess);
+    }
+
+    (void) RemoveCertFromSPKDigestTableForAlg(handle, cert, SEC_OID_MD2);
+    (void) RemoveCertFromSPKDigestTableForAlg(handle, cert, SEC_OID_MD5);
+    return RemoveCertFromSPKDigestTableForAlg(handle, cert, SEC_OID_SHA1);
+}
+
+/*
+ * Add the spk digest for the given cert to the spk digest table,
+ * based on the given digest algorithm.
+ *
+ * If a cert for the same spk digest is already in the table, choose whichever
+ * cert is "newer".  (The other cert cannot be found via spk digest.)
+ *
+ * The database must be locked already.
+ * 
+ * XXX Note that this implementation results in leaking the index value.
+ * Fixing that did not seem worth the trouble, given we will only leak
+ * once per cert.  This whole thing should be done differently in the
+ * new rewrite (Stan), and then the problem will go away.
+ */
+static SECStatus
+AddCertToSPKDigestTableForAlg(CERTCertDBHandle *handle, CERTCertificate *cert,
+			      SECItem *certDBKey, SECOidTag digestAlg)
+{
+    SECStatus rv = SECFailure;
+    SECItem *oldCertDBKey;
+    PRBool addit = PR_TRUE;
+    CERTCertificate *oldCert = NULL;
+    char *index = NULL;
+    PLHashTable *table;
+
+    /*
+     * After running some testing doing key hash lookups (like using OCSP),
+     * if these are never hit, they can probably be removed.
+     */
+    PORT_Assert(handle != NULL);
+    PORT_Assert(handle == cert->dbhandle);
+    PORT_Assert(handle->spkDigestInfo != NULL);
+    PORT_Assert((certDBKey == &cert->certKey)
+		|| (SECITEM_CompareItem(certDBKey,
+					&cert->certKey) == SECEqual));
+
+    table = SPK_DIGEST_TABLE(handle);
+    PORT_Assert(table != NULL);
+
+    index = spkDigestIndexFromCert(cert, digestAlg);
+    if ( index == NULL ) {
+	goto loser;
+    }
+
+    /*
+     * See if this cert's spk digest is already in the table.
+     */
+    oldCertDBKey = PL_HashTableLookup(table, index);
+    if ( oldCertDBKey != NULL ) {
+	/*
+	 * The spk digest *is* already in the table.  We need to find that
+	 * cert and see -- if it is the same, then we can just leave as is.
+	 * Otherwise we have to choose which cert we want represented;
+	 * in that case the best plan I can think of is to hang onto the
+	 * most recent one.
+	 */
+	oldCert = CERT_FindCertByKey(handle, oldCertDBKey);
+	if ( oldCert != NULL ) {
+	    if ( cert == oldCert ) {
+		/* They are the same cert, so we are done. */
+		addit = PR_FALSE;
+	    } else if ( CERT_IsNewer(cert, oldCert) ) {
+		if ( PL_HashTableRemove(table, index) != PR_TRUE ) {
+		    goto loser;
+		}
+	    } else {
+		/* oldCert is "newer", so we are done. */
+		addit = PR_FALSE;
+	    }
+	}
+    }
+
+    if ( addit ) {
+	certDBKey = SECITEM_DupItem(certDBKey);
+	if ( certDBKey == NULL ) {
+	    goto loser;
+	}
+	if ( PL_HashTableAdd(table, index, certDBKey) == NULL ) {
+	    SECITEM_FreeItem(certDBKey, PR_TRUE);
+	    PORT_SetError(SEC_ERROR_NO_MEMORY);
+	    goto loser;
+	}
+	index = NULL;				/* don't want to free it */
+    }
+
+    rv = SECSuccess;
+
+loser:
+    if ( index != NULL ) {
+	PORT_Free(index);
+    }
+    if ( oldCert != NULL ) {
+	CERT_DestroyCertificate(oldCert);
+    }
+    return(rv);
+}
+
+/*
+ * Add the spk digest for the given cert to the spk digest table,
+ * for all known digest algorithms.
+ *
+ * The database must be locked already, and the digest table already created.
+ */
+static SECStatus
+AddCertToSPKDigestTableForAllAlgs(CERTCertDBHandle *handle,
+				  CERTCertificate *cert, SECItem *certDBKey)
+{
+    (void) AddCertToSPKDigestTableForAlg(handle, cert, certDBKey, SEC_OID_MD2);
+    (void) AddCertToSPKDigestTableForAlg(handle, cert, certDBKey, SEC_OID_MD5);
+    return AddCertToSPKDigestTableForAlg(handle, cert, certDBKey, SEC_OID_SHA1);
+}
+
+/*
+ * Add the spk digests for the given cert to the spk digest table,
+ * for all known digest algorithms.  (This function is called when a
+ * new cert is added to the temporary database.)
+ *
+ * If the spk digest table does not yet exist, create it.
+ *
+ * In an ideal world, we would not hardwire the digest algorithms.
+ * But it is the case that we do not currently support any digest
+ * algorithms outside of these three.  In the newer, cleaned-up world,
+ * this may be done differently.
+ *
+ * The database must be locked already.
+ */
+static SECStatus
+AddCertToSPKDigestTable(CERTCertDBHandle *handle, CERTCertificate *cert)
+{
+    SECStatus rv;
+
+    if ( handle->spkDigestInfo == NULL ) {
+	rv = InitDBspkDigestInfo(handle);
+	if ( rv != SECSuccess ) {
+	    return(rv);
+	}
+    }
+
+    return AddCertToSPKDigestTableForAllAlgs(handle, cert, &cert->certKey);
+}
+
+/*
+ * Add the spk digest for the given cert to the spk digest table,
+ * for all known digest algorithms.  This function is called while
+ * traversing all of the certs in the permanent database -- since
+ * that imposes some constraints on its arguments this routine is a
+ * simple cover for the "real" interface.
+ *
+ * The database must be locked already, and the digest table already created.
+ */
+static SECStatus
+AddCertToSPKDigestTableInTraversal(CERTCertificate *cert, SECItem *certDBKey,
+				   void *data)
+{
+    CERTCertDBHandle *handle = data;
+
+    return AddCertToSPKDigestTableForAllAlgs(handle, cert, certDBKey);
+}
+
+/*
+ * Add the spk digests for the all permanent certs to the spk digest table,
+ * for all known digest algorithms.
+ *
+ * This locks the database, and then checks to make sure that the work
+ * actually needs to get done.
+ *
+ * If the spk digest table does not yet exist, it is created.
+ */
+static SECStatus
+PopulateSPKDigestTable(CERTCertDBHandle *handle)
+{
+    SPKDigestInfo *spkDigestInfo;
+    SECStatus rv = SECSuccess;
+
+    CERT_LockDB(handle);
+
+    spkDigestInfo = handle->spkDigestInfo;
+    if ( spkDigestInfo == NULL ) {
+	rv = InitDBspkDigestInfo(handle);
+	if ( rv != SECSuccess ) {
+	    return(rv);
+	}
+	spkDigestInfo = handle->spkDigestInfo;
+	PORT_Assert(spkDigestInfo != NULL);
+    } else {
+	/*
+	 * Check to see if someone already did it; it is important to do
+	 * this after getting the lock.
+	 */
+	if ( spkDigestInfo->permPopulated == PR_TRUE ) {
+	    goto done;
+	}
+    }
+
+    rv = TraversePermCertsNoLocking(handle, AddCertToSPKDigestTableInTraversal,
+				    handle);
+
+    if ( rv != SECSuccess ) {
+	goto done;
+    }
+
+    spkDigestInfo->permPopulated = PR_TRUE;
+
+done:
+    CERT_UnlockDB(handle);
+
+    return(rv);
+}
+
+/*
+ * Lookup a certificate by a digest of a subject public key.  If it is
+ * found, it is returned (and must then be destroyed by the caller).
+ * NULL is returned otherwise -- if there was a problem performing the
+ * lookup, an appropriate error is set (e.g. SEC_ERROR_NO_MEMORY);
+ * if the cert simply was not found, the error is SEC_ERROR_UNKNOWN_CERT.
+ *
+ * If the lookup table has not yet been created or populated, do that first.
+ */
+CERTCertificate *
+CERT_FindCertBySPKDigest(CERTCertDBHandle *handle, SECItem *spkDigest)
+{
+    SPKDigestInfo *spkDigestInfo;
+    char *index = NULL;
+    SECItem *certDBKey;
+    CERTCertificate *cert = NULL;
+
+    PORT_Assert(handle != NULL);
+    spkDigestInfo = handle->spkDigestInfo;
+
+    if ( spkDigestInfo == NULL || spkDigestInfo->permPopulated != PR_TRUE ) {
+	if ( PopulateSPKDigestTable(handle) != SECSuccess ) {
+	    goto loser;
+	}
+    }
+
+    index = spkDigestIndexFromDigest(spkDigest);
+    if ( index == NULL ) {
+	goto loser;
+    }
+
+    certDBKey = PL_HashTableLookup(SPK_DIGEST_TABLE(handle), index);
+    if ( certDBKey != NULL ) {
+	cert = CERT_FindCertByKey(handle, certDBKey);
+    }
+
+    if ( cert == NULL ) {
+	PORT_SetError(SEC_ERROR_UNKNOWN_CERT);
+    }
+
+loser:
+    if ( index != NULL ) {
+	PORT_Free(index);
+    }
+    return(cert);
+}
+
+static void
+DestroyCertificate(CERTCertificate *cert, PRBool lockdb)
+{
+    int refCount;
+    CERTCertDBHandle *handle;
+    
+    if ( cert ) {
+
+	handle = cert->dbhandle;
+
+	/*
+	 * handle may be NULL, for example if the cert was created with
+	 * CERT_DecodeDERCertificate.
+	 */
+	if ( lockdb && handle ) {
+	    CERT_LockDB(handle);
+	}
+
+        CERT_LockCertRefCount(cert);
+	PORT_Assert(cert->referenceCount > 0);
+	refCount = --cert->referenceCount;
+        CERT_UnlockCertRefCount(cert);
+
+	if ( ( refCount == 0 ) && !cert->keepSession ) {
+	    certDBEntryCert *entry  = cert->dbEntry;
+	    PRArenaPool *    arena  = cert->arena;
+
+	    if ( cert->istemp ) {
+		CERT_DeleteTempCertificate(cert);
+	    }
+
+	    if ( entry ) {
+		DestroyDBEntry((certDBEntry *)entry);
+            }
+
+	    /* zero cert before freeing. Any stale references to this cert
+	     * after this point will probably cause an exception.  */
+	    PORT_Memset(cert, 0, sizeof *cert);
+
+	    cert = NULL;
+	    
+	    /* free the arena that contains the cert. */
+	    PORT_FreeArena(arena, PR_FALSE);
+        }
+	if ( lockdb && handle ) {
+	    CERT_UnlockDB(handle);
+	}
+    }
+
+    return;
+}
+
+void
+CERT_DestroyCertificate(CERTCertificate *cert)
+{
+    DestroyCertificate(cert, PR_TRUE);
+    return;
+}
+
+void
+CERT_DestroyCertificateNoLocking(CERTCertificate *cert)
+{
+    DestroyCertificate(cert, PR_FALSE);
+    return;
+}
+
+/*
+ * cache a CRL from the permanent database into the temporary database
+ */
+CERTSignedCrl *
+SEC_AddPermCrlToTemp(CERTCertDBHandle *handle, certDBEntryRevocation *entry)
+{
+    CERTSignedCrl *crl = NULL;
+    DBT key;
+    DBT data;
+    int status;
+    PRArenaPool *arena = NULL;
+    SECItem keyitem;
+    SECStatus rv;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+    
+    crl = CERT_DecodeDERCrl(NULL, &entry->derCrl, 
+       entry->common.type == certDBEntryTypeRevocation ? 
+						SEC_CRL_TYPE : SEC_KRL_TYPE);
+    
+    if ( crl == NULL ) {
+	goto loser;
+    }
+
+    /* only save pointer to cert in database */
+    data.data = &crl;
+    data.size = sizeof(crl);
+
+
+    rv = EncodeDBGenericKey(&(crl->crl.derName), arena, 
+						&keyitem, entry->common.type);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+
+    if (entry->url) {
+	int nnlen = PORT_Strlen(entry->url) + 1;
+	crl->url  = (char *)PORT_ArenaAlloc(crl->arena, nnlen);
+	if ( !crl->url ) {
+	    goto loser;
+	}
+	PORT_Memcpy(crl->url, entry->url, nnlen);
+    } else {
+	crl->url = NULL;
+    }
+    
+    key.data = keyitem.data;
+    key.size = keyitem.len;
+    
+    /* enter into main db */
+    status = certdb_Put(handle->tempCertDB, &key, &data, R_NOOVERWRITE);
+    if ( status ) {
+	goto loser;
+    }
+
+    crl->istemp = PR_TRUE;
+    crl->isperm = PR_TRUE;    
+    crl->dbhandle = handle;
+    crl->dbEntry = entry;
+
+    
+    PORT_FreeArena(arena, PR_FALSE);
+
+    crl->keep = PR_TRUE;
+    return(crl);
+
+loser:
+    if ( crl ) {
+	SEC_DestroyCrl(crl);
+    }
+    
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    PORT_SetError(SEC_ERROR_BAD_DATABASE);
+    return(0);
+}
+
+SECStatus
+SEC_DeleteTempCrl(CERTSignedCrl *crl)
+{
+    SECStatus rv;
+    DBT nameKey;
+    CERTCertDBHandle *handle;
+    SECItem keyitem;
+    PRArenaPool *arena;
+    int ret;
+    
+    handle = crl->dbhandle;
+
+    if ( !crl->istemp ) {
+	return(SECSuccess);
+    }
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+
+    rv = EncodeDBGenericKey
+	 (&crl->crl.derName, arena, &keyitem, crl->dbEntry->common.type);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    nameKey.data = keyitem.data;
+    nameKey.size = keyitem.len;
+
+    /* delete the cert */
+    ret = certdb_Del(handle->tempCertDB, &nameKey, 0);
+    if ( ret ) {
+	goto loser;
+    }
+
+    crl->istemp = PR_FALSE;
+
+    PORT_FreeArena(arena, PR_FALSE);
+    return(SECSuccess);
+
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+
+    return(SECFailure);
+}
+
+/*
+ * Lookup a CRL in the databases. We mirror the same fast caching data base
+ *  caching stuff used by certificates....?
+ */
+CERTSignedCrl *
+SEC_FindCrlByKey(CERTCertDBHandle *handle, SECItem *crlKey, int type)
+{
+    DBT tmpdata;
+    int ret;
+    SECItem keyitem;
+    DBT key;
+    SECStatus rv;
+    CERTSignedCrl *crl = NULL;
+    PRArenaPool *arena = NULL;
+    certDBEntryRevocation *entry;
+    certDBEntryType crlType = (type == SEC_CRL_TYPE) ?
+	 certDBEntryTypeRevocation : certDBEntryTypeKeyRevocation;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+    
+    rv = EncodeDBGenericKey(crlKey, arena, &keyitem, crlType);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+    
+    key.data = keyitem.data;
+    key.size = keyitem.len;
+    
+    /* lookup in the temporary database */
+    ret = certdb_Get( handle->tempCertDB, &key, &tmpdata, 0 );
+
+    /* error accessing the database */
+    if ( ret < 0 ) {
+	PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	goto loser;
+    }
+
+    if ( ret == 0 ) {	/* found in temp database */
+	if ( tmpdata.size != sizeof(CERTSignedCrl *) ) {
+	    PORT_SetError(SEC_ERROR_BAD_DATABASE);
+	    goto loser;
+	}
+	
+	PORT_Memcpy(&crl, tmpdata.data, tmpdata.size);
+	crl->referenceCount++;
+    } else {		/* not found in temporary database */
+
+	/* find in perm database */
+	entry = ReadDBCrlEntry(handle, crlKey, crlType);
+	
+	if ( entry == NULL ) {
+	    goto loser;
+	}
+    
+	crl = SEC_AddPermCrlToTemp(handle, entry);
+    }
+
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    
+    return(crl);
+}
+
+
+CERTSignedCrl *
+SEC_FindCrlByName(CERTCertDBHandle *handle, SECItem *crlKey, int type)
+{
+	return SEC_FindCrlByKey(handle,crlKey,type);
+}
+
+CERTSignedCrl *
+SEC_FindCrlByDERCert(CERTCertDBHandle *handle, SECItem *derCrl, int type)
+{
+    PRArenaPool *arena;
+    SECItem crlKey;
+    SECStatus rv;
+    CERTSignedCrl *crl = NULL;
+    
+    /* create a scratch arena */
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	return(NULL);
+    }
+    
+    /* extract the database key from the cert */
+    rv = CERT_KeyFromDERCrl(arena, derCrl, &crlKey);
+    if ( rv != SECSuccess ) {
+	goto loser;
+    }
+
+    /* find the crl */
+    crl = SEC_FindCrlByKey(handle, &crlKey, type);
+    
+loser:
+    PORT_FreeArena(arena, PR_FALSE);
+    return(crl);
+}
+
+
+SECStatus
+SEC_DestroyCrl(CERTSignedCrl *crl)
+{
+    if (crl) {
+	if (crl->referenceCount-- <= 1) {
+	    if (!crl->keep) {
+		SEC_DeleteTempCrl(crl);
+		if (crl->dbEntry) {
+		    DestroyDBEntry((certDBEntry *)crl->dbEntry);
+		}
+		PORT_FreeArena(crl->arena, PR_FALSE);
+	    }
+	}
+    }
+    return SECSuccess;
+}
+
+CERTSignedCrl *
+cert_DBInsertCRL (CERTCertDBHandle *handle, char *url,
+		  CERTSignedCrl *newCrl, SECItem *derCrl, int type)
+{
+    CERTSignedCrl *oldCrl = NULL, *crl = NULL;
+    certDBEntryRevocation *entry = NULL;
+    PRArenaPool *arena = NULL;
+    SECCertTimeValidity validity;
+    certDBEntryType crlType = (type == SEC_CRL_TYPE) ?
+	 certDBEntryTypeRevocation : certDBEntryTypeKeyRevocation;
+    SECStatus rv;
+
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if (arena == NULL) goto done;
+
+    validity = SEC_CheckCrlTimes(&newCrl->crl,PR_Now());
+    if ( validity == secCertTimeExpired) {
+	if (type == SEC_CRL_TYPE) {
+	    PORT_SetError(SEC_ERROR_CRL_EXPIRED);
+	} else {
+	    PORT_SetError(SEC_ERROR_KRL_EXPIRED);
+	}
+	goto done;
+    } else if (validity == secCertTimeNotValidYet) {
+	if (type == SEC_CRL_TYPE) {
+	    PORT_SetError(SEC_ERROR_CRL_NOT_YET_VALID);
+	} else {
+	    PORT_SetError(SEC_ERROR_KRL_NOT_YET_VALID);
+	}
+	goto done;
+    }
+
+    oldCrl = SEC_FindCrlByKey(handle, &newCrl->crl.derName, type);
+
+    /* if there is an old crl, make sure the one we are installing
+     * is newer. If not, exit out, otherwise delete the old crl.
+     */
+    if (oldCrl != NULL) {
+	if (!SEC_CrlIsNewer(&newCrl->crl,&oldCrl->crl)) {
+
+	    if (type == SEC_CRL_TYPE) {
+		PORT_SetError(SEC_ERROR_OLD_CRL);
+	    } else {
+		PORT_SetError(SEC_ERROR_OLD_KRL);
+	    }
+
+	    goto done;
+	}
+
+	if ((SECITEM_CompareItem(&newCrl->crl.derName, 
+	        &oldCrl->crl.derName) != SECEqual) &&
+	    (type == SEC_KRL_TYPE) ) {
+	    
+	    PORT_SetError(SEC_ERROR_CKL_CONFLICT);
+	    goto done;
+	}
+
+	/* if we have a url in the database, use that one */
+	if (oldCrl->url) {
+	    int nnlen = PORT_Strlen(oldCrl->url) + 1;
+	    url  = (char *)PORT_ArenaAlloc(arena, nnlen);
+	    if ( url != NULL ) {
+	        PORT_Memcpy(url, oldCrl->url, nnlen);
+	    }
+	}
+
+
+	/* really destroy this crl */
+	/* first drum it out of the permanment Data base */
+	SEC_DeletePermCRL(oldCrl);
+	/* then get rid of our reference to it... */
+	SEC_DestroyCrl(oldCrl);
+	oldCrl = NULL;
+
+    }
+
+    /* Write the new entry into the data base */
+    entry = NewDBCrlEntry(derCrl, url, crlType, 0);
+    if (entry == NULL) goto done;
+
+    rv = WriteDBCrlEntry(handle, entry);
+    if (rv != SECSuccess) goto done;
+
+    crl = SEC_AddPermCrlToTemp(handle, entry);
+    if (crl) entry = NULL; /*crl->dbEntry now points to entry data */
+    crl->isperm = PR_TRUE;
+
+done:
+    if (entry) DestroyDBEntry((certDBEntry *)entry);
+    if (arena) PORT_FreeArena(arena, PR_FALSE);
+    if (oldCrl) SEC_DestroyCrl(oldCrl);
+
+    return crl;
+}
+
+
+/*
+ * create a new CRL from DER material.
+ *
+ * The signature on this CRL must be checked before you
+ * load it. ???
+ */
+CERTSignedCrl *
+SEC_NewCrl(CERTCertDBHandle *handle, char *url, SECItem *derCrl, int type)
+{
+    CERTSignedCrl *newCrl = NULL, *crl = NULL;
+
+    /* make this decode dates! */
+    newCrl = CERT_DecodeDERCrl(NULL, derCrl, type);
+    if (newCrl == NULL) {
+	if (type == SEC_CRL_TYPE) {
+	    PORT_SetError(SEC_ERROR_CRL_INVALID);
+	} else {
+	    PORT_SetError(SEC_ERROR_KRL_INVALID);
+	}
+	goto done;
+    }
+
+    crl = cert_DBInsertCRL (handle, url, newCrl, derCrl, type);
+
+
+done:
+    if (newCrl) PORT_FreeArena(newCrl->arena, PR_FALSE);
+
+    return crl;
+}
+
+
+/*
+ * replace the existing URL in the data base with a new one
+ */
+SECStatus
+SEC_CrlReplaceUrl(CERTSignedCrl *crl,char *url) {
+    SECStatus rv = SECFailure;
+    certDBEntryRevocation *entry = NULL;
+    int nnlen=0;
+
+    SEC_DeletePermCRL(crl);
+
+    /* Write the new entry into the data base */
+    entry = NewDBCrlEntry(&crl->dbEntry->derCrl, url, crl->dbEntry->common.type, 0);
+    if (entry == NULL) goto done;
+
+    rv = WriteDBCrlEntry(crl->dbhandle, entry);
+    if (rv != SECSuccess) goto done;
+
+    if (url) {
+	nnlen = PORT_Strlen(url) + 1;
+	crl->url  = (char *)PORT_ArenaAlloc(crl->arena, nnlen);
+	if ( !crl->url ) {
+	    goto done;
+	}
+	PORT_Memcpy(crl->url, url, nnlen);
+    } else {
+	crl->url = NULL;
+    }
+done:
+    return rv;
+}
+    
+
+/*
+ * collect a linked list of CRL's
+ */
+static SECStatus CollectCrls(SECItem *dbdata, SECItem *dbkey, 
+					certDBEntryType type, void *data) {
+    SECStatus rv;
+    certDBEntryRevocation entry;
+    SECItem entryitem;
+    PRArenaPool *arena = NULL;
+    CERTCrlHeadNode *head;
+    CERTCrlNode *new_node;
+    
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	goto loser;
+    }
+    
+    head = (CERTCrlHeadNode *)data;
+    entry.common.version = (unsigned int)dbdata->data[0];
+    entry.common.type = (certDBEntryType)dbdata->data[1];
+    entry.common.flags = (unsigned int)dbdata->data[2];
+    entry.common.arena = arena;
+    
+    entryitem.len = dbdata->len - SEC_DB_ENTRY_HEADER_LEN;
+    entryitem.data = &dbdata->data[SEC_DB_ENTRY_HEADER_LEN];
+    
+    rv = DecodeDBCrlEntry(&entry, &entryitem);
+    if (rv != SECSuccess ) {
+	goto loser;
+    }
+
+    new_node = (CERTCrlNode *)PORT_ArenaAlloc(head->arena, sizeof(CERTCrlNode));
+    if (new_node == NULL) {
+	goto loser;
+    }
+
+    new_node->type = (entry.common.type == certDBEntryTypeRevocation) ? 
+						SEC_CRL_TYPE : SEC_KRL_TYPE;
+    new_node->crl=CERT_DecodeDERCrl(head->arena,&entry.derCrl,new_node->type);
+
+    if (entry.url) {
+	int nnlen = PORT_Strlen(entry.url) + 1;
+	new_node->crl->url  = (char *)PORT_ArenaAlloc(head->arena, nnlen);
+	if ( !new_node->crl->url ) {
+	    goto loser;
+	}
+	PORT_Memcpy(new_node->crl->url, entry.url, nnlen);
+    } else {
+	new_node->crl->url = NULL;
+    }
+    
+
+    new_node->next = NULL;
+    if (head->last) {
+	head->last->next = new_node;
+	head->last = new_node;
+    } else {
+	head->first = head->last = new_node;
+    }
+    return (SECSuccess);
+    
+loser:
+    if ( arena ) {
+	PORT_FreeArena(arena, PR_FALSE);
+    }
+    return(SECFailure);
+}
+
+	
+SECStatus
+SEC_LookupCrls(CERTCertDBHandle *handle, CERTCrlHeadNode **nodes, int type)
+{
+    CERTCrlHeadNode *head;
+    PRArenaPool *arena = NULL;
+    SECStatus rv;
+
+    *nodes = NULL;
+
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if ( arena == NULL ) {
+	return SECFailure;
+    }
+
+    /* build a head structure */
+    head = (CERTCrlHeadNode *)PORT_ArenaAlloc(arena, sizeof(CERTCrlHeadNode));
+    head->arena = arena;
+    head->first = NULL;
+    head->last = NULL;
+    head->dbhandle = handle;
+
+    /* Look up the proper crl types */
+    *nodes = head;
+    
+    CERT_LockDB(handle);
+    
+    switch (type) {
+    case SEC_CRL_TYPE:
+	rv = SEC_TraverseDBEntries(handle, certDBEntryTypeRevocation,
+						CollectCrls, (void *)head);
+	break;
+    case SEC_KRL_TYPE:
+	rv = SEC_TraverseDBEntries(handle, certDBEntryTypeKeyRevocation,
+					CollectCrls, (void *)head);
+	break;
+    case -1:
+	rv = SEC_TraverseDBEntries(handle, certDBEntryTypeKeyRevocation,
+						CollectCrls, (void *)head);
+	if (rv != SECSuccess) break;
+	rv = SEC_TraverseDBEntries(handle, certDBEntryTypeRevocation,
+					CollectCrls, (void *)head);
+	break;
+    default:
+	rv = SECFailure;
+	break;
+    }
+
+    CERT_UnlockDB(handle);
+    
+    if (rv != SECSuccess) {
+	if ( arena ) {
+	    PORT_FreeArena(arena, PR_FALSE);
+	    *nodes = NULL;
+	}
+    }
+
+    return rv;
+}
+	
+
+SECStatus
+SEC_DeletePermCRL(CERTSignedCrl *crl) {
+    SECStatus rv;
+
+    if (crl == NULL) {
+	return SECFailure;
+    }
+    
+    rv = DeleteDBCrlEntry(crl->dbhandle, &crl->crl.derName, 
+					crl->dbEntry->common.type);
+    if (rv != SECSuccess) goto done;
+
+    /* now force it to be freed when all the reference counts go */
+    crl->keep = PR_FALSE;
+    /* force it out of the temporary data base */
+    SEC_DeleteTempCrl(crl);
+  
+done:
+    return rv;
+}
+
+/*
+ * find a cert by email address
+ *
+ * pick one that is a valid recipient, meaning that it is an encryption
+ * cert.
+ *
+ */
+static CERTCertificate*
+find_smime_recipient_cert(CERTCertDBHandle* handle, const char* email_addr)
+{
+    CERTCertificate* cert = NULL;
+    CERTCertList* certList = NULL;
+    SECStatus rv;
+
+    certList = CERT_CreateEmailAddrCertList(NULL, handle, (char*)email_addr,
+					    PR_Now(), PR_TRUE);
+    if (certList == NULL) {
+	return NULL;
+    }
+
+    rv = CERT_FilterCertListByUsage(certList, certUsageEmailRecipient,
+				    PR_FALSE);
+
+    if (!CERT_LIST_END(CERT_LIST_HEAD(certList), certList)) {
+	cert = CERT_DupCertificate(CERT_LIST_HEAD(certList)->cert);
+    }
+
+    CERT_DestroyCertList(certList);
+
+    return cert;    /* cert may point to a cert or may be NULL */
+}
+
+/*
+ * This function has the logic that decides if another person's cert and
+ * email profile from an S/MIME message should be saved.  It can deal with
+ * the case when there is no profile.
+ */
+SECStatus
+CERT_SaveSMimeProfile(CERTCertificate *cert, SECItem *emailProfile,
+		      SECItem *profileTime)
+{
+    certDBEntrySMime *entry = NULL, *oldentry = NULL;
+    int64 oldtime;
+    int64 newtime;
+    SECStatus rv;
+    CERTCertificate *oldcert = NULL;
+    PRBool saveit;
+    CERTCertTrust trust;
+    CERTCertTrust tmptrust;
+    char *emailAddr;
+    
+    emailAddr = cert->emailAddr;
+    
+    PORT_Assert(emailAddr);
+    if ( emailAddr == NULL ) {
+	goto loser;
+    }
+
+    saveit = PR_FALSE;
+    
+    oldcert = find_smime_recipient_cert(cert->dbhandle, emailAddr);
+	if (oldcert) {
+	    /* see if there is an entry already */
+		oldentry = ReadDBSMimeEntry(cert->dbhandle, emailAddr);
+	}
+    
+    /* both profileTime and emailProfile have to exist or not exist */
+    if ( emailProfile == NULL ) {
+	profileTime = NULL;
+    } else if ( profileTime == NULL ) {
+	emailProfile = NULL;
+    }
+   
+    if ( oldentry == NULL ) {
+	/* no old entry for this address */
+	PORT_Assert(oldcert == NULL);
+	saveit = PR_TRUE;
+    } else {
+	/* there was already a profile for this email addr */
+	if ( profileTime ) {
+	    /* we have an old and new profile - save whichever is more recent*/
+	    if ( oldentry->optionsDate.len == 0 ) {
+		/* always replace if old entry doesn't have a time */
+		oldtime = LL_MININT;
+	    } else {
+		rv = DER_UTCTimeToTime(&oldtime, &oldentry->optionsDate);
+		if ( rv != SECSuccess ) {
+		    goto loser;
+		}
+	    }
+	    
+	    rv = DER_UTCTimeToTime(&newtime, profileTime);
+	    if ( rv != SECSuccess ) {
+		goto loser;
+	    }
+	
+	    if ( LL_CMP(newtime, >, oldtime ) ) {
+		/* this is a newer profile, save it and cert */
+		saveit = PR_TRUE;
+	    }
+	} else {
+	    /* we don't have a new profile or time */
+	    if ( oldentry->optionsDate.len == 0 ) {
+		/* the old entry doesn't have a time either, so compare certs*/
+		if ( CERT_IsNewer(cert, oldcert) ) {
+		    /* new cert is newer, use it instead */
+		    saveit = PR_TRUE;
+		}
+	    } else {
+		if (oldcert) {
+			if (CERT_IsNewer(cert, oldcert)) {
+				saveit = PR_TRUE;
+			}
+		} else {
+			saveit = PR_TRUE;
+		}
+	    }
+	}
+    }
+
+    if ( saveit ) {
+	if ( oldcert && ( oldcert != cert ) ) {
+	    /* old cert is different from new cert */
+	    if ( PORT_Memcmp(oldcert->trust, &trust, sizeof(trust)) == 0 ) {
+		/* old cert is only for e-mail, so delete it */
+		SEC_DeletePermCertificate(oldcert);
+	    } else {
+		/* old cert is for other things too, so just change trust */
+		tmptrust = *oldcert->trust;
+		tmptrust.emailFlags &= ( ~CERTDB_VALID_PEER );
+		rv = CERT_ChangeCertTrust(oldcert->dbhandle, oldcert,
+					  &tmptrust);
+		if ( rv != SECSuccess ) {
+		    goto loser;
+		}
+	    }
+	}
+
+/* Subroutine */
+	/* now save the entry */
+	entry = NewDBSMimeEntry(emailAddr, &cert->derSubject, emailProfile,
+				profileTime, 0);
+	if ( entry == NULL ) {
+	    goto loser;
+	}
+
+	CERT_LockDB(cert->dbhandle);
+
+	rv = DeleteDBSMimeEntry(cert->dbhandle, emailAddr);
+	/* if delete fails, try to write new entry anyway... */
+	
+	rv = WriteDBSMimeEntry(cert->dbhandle, entry);
+	if ( rv != SECSuccess ) {
+	    CERT_UnlockDB(cert->dbhandle);
+	    goto loser;
+	}
+
+	/* link subject entry back here */
+	rv = UpdateSubjectWithEmailAddr(cert, emailAddr);
+	if ( rv != SECSuccess ) {
+	    CERT_UnlockDB(cert->dbhandle);
+	    goto loser;
+	}
+
+	CERT_UnlockDB(cert->dbhandle);
+/* End Subroutine */
+    }
+
+    rv = SECSuccess;
+    goto done;
+    
+loser:
+    rv = SECFailure;
+    
+done:
+    if ( oldcert ) {
+	CERT_DestroyCertificate(oldcert);
+    }
+    
+    if ( entry ) {
+	DestroyDBEntry((certDBEntry *)entry);
+    }
+    if ( oldentry ) {
+	DestroyDBEntry((certDBEntry *)oldentry);
+    }
+    
+    return(rv);
+}
+
+CERTCertificate *
+CERT_FindCertByEmailAddr(CERTCertDBHandle *handle, char *emailAddr)
+{
+    certDBEntrySMime *entry;
+    CERTCertificate *cert = NULL;
+
+    emailAddr = CERT_FixupEmailAddr(emailAddr);
+    if ( emailAddr == NULL ) {
+	return(NULL);
+    }
+    
+    entry = ReadDBSMimeEntry(handle, emailAddr);
+
+    /* XXX - this will have to change when multiple certs per subject
+     * are allowed
+     */
+    if ( entry != NULL ) {
+	cert = CERT_FindCertByName(handle, &entry->subjectName);
+    }
+
+    if ( entry ) {
+	DestroyDBEntry((certDBEntry *)entry);
+    }
+
+    PORT_Free(emailAddr);
+    
+    return(cert);
+}
+
+/*
+ * find the smime symmetric capabilities profile for a given cert
+ */
+SECItem *
+CERT_FindSMimeProfile(CERTCertificate *cert)
+{
+    certDBEntrySMime *entry;
+    SECItem *retitem = NULL;
+    
+    PORT_Assert(cert->emailAddr != NULL);
+    
+    if ( cert->emailAddr == NULL ) {
+	return(NULL);
+    }
+    
+    entry = ReadDBSMimeEntry(cert->dbhandle, cert->emailAddr);
+
+    if ( entry ) {
+	/*  May not be for this cert...  */
+	if (SECITEM_ItemsAreEqual(&cert->derSubject, &entry->subjectName))
+	    retitem = SECITEM_DupItem(&entry->smimeOptions);
+	DestroyDBEntry((certDBEntry *)entry);
+    }
+
+    return(retitem);
+}
+
+CERTCertificate *
+CERT_FindCertByNicknameOrEmailAddr(CERTCertDBHandle *handle, char *name)
+{
+    CERTCertificate *cert;
+    cert = CERT_FindCertByNickname(handle, name);
+    if ( cert == NULL ) {
+	cert = CERT_FindCertByEmailAddr(handle, name);
+    }
+
+    return(cert);
+}
+
+PRBool
+CERT_IsCertRevoked(CERTCertificate *cert)
+{
+    return(PR_FALSE);
+}
+
+CERTCertificate *
+CERT_NextSubjectCert(CERTCertificate *cert)
+{
+    CERTSubjectNode *node;
+    CERTCertificate *retcert = NULL;
+
+    CERT_LockDB(cert->dbhandle);
+    
+    node = FindCertSubjectNode(cert);
+    PORT_Assert(node != NULL);
+    
+    if ( node->next != NULL ) {
+	retcert = CERT_FindCertByKeyNoLocking(cert->dbhandle,
+					      &node->next->certKey);
+    }
+
+    CERT_UnlockDB(cert->dbhandle);
+
+    return(retcert);
+}
+
+CERTCertificate *
+CERT_PrevSubjectCert(CERTCertificate *cert)
+{
+    CERTSubjectNode *node;
+    CERTCertificate *retcert = NULL;
+    
+    CERT_LockDB(cert->dbhandle);
+    node = FindCertSubjectNode(cert);
+    PORT_Assert(node != NULL);
+    
+    if ( node->prev != NULL ) {
+	retcert = CERT_FindCertByKeyNoLocking(cert->dbhandle,
+					      &node->prev->certKey);
+    }
+    CERT_UnlockDB(cert->dbhandle);
+
+    return(retcert);
+}
+
+SECStatus
+CERT_SaveImportedCert(CERTCertificate *cert, SECCertUsage usage,
+		      PRBool caOnly, char *nickname)
+{
+    SECStatus rv;
+    PRBool saveit;
+    CERTCertTrust trust;
+    CERTCertTrust tmptrust;
+    PRBool isCA;
+    unsigned int certtype;
+    PRBool freeNickname = PR_FALSE;
+    
+    isCA = CERT_IsCACert(cert, NULL);
+    if ( caOnly && ( !isCA ) ) {
+	return(SECSuccess);
+    }
+    
+    saveit = PR_TRUE;
+    
+    PORT_Memset((void *)&trust, 0, sizeof(trust));
+
+    certtype = cert->nsCertType;
+
+    /* if no CA bits in cert type, then set all CA bits */
+    if ( isCA && ( ! ( certtype & NS_CERT_TYPE_CA ) ) ) {
+	certtype |= NS_CERT_TYPE_CA;
+    }
+
+    /* if no app bits in cert type, then set all app bits */
+    if ( ( !isCA ) && ( ! ( certtype & NS_CERT_TYPE_APP ) ) ) {
+	certtype |= NS_CERT_TYPE_APP;
+    }
+
+    if ( isCA && !nickname ) {
+	nickname = CERT_MakeCANickname(cert);
+	if ( nickname != NULL ) {
+	    freeNickname = PR_TRUE;
+	}
+    }
+    
+    switch ( usage ) {
+      case certUsageEmailSigner:
+      case certUsageEmailRecipient:
+	if ( isCA ) {
+	    if ( certtype & NS_CERT_TYPE_EMAIL_CA ) {
+		trust.emailFlags = CERTDB_VALID_CA;
+	    }
+	} else {
+	    PORT_Assert(nickname == NULL);
+
+	    if ( cert->emailAddr == NULL ) {
+		saveit = PR_FALSE;
+	    }
+	    
+	    if ( certtype & NS_CERT_TYPE_EMAIL ) {
+		trust.emailFlags = CERTDB_VALID_PEER;
+		if ( ! ( cert->rawKeyUsage & KU_KEY_ENCIPHERMENT ) ) {
+		    /* don't save it if KeyEncipherment is not allowed */
+		    saveit = PR_FALSE;
+		}
+	    }
+	}
+	break;
+      case certUsageUserCertImport:
+	if ( isCA ) {
+	    if ( certtype & NS_CERT_TYPE_SSL_CA ) {
+		trust.sslFlags = CERTDB_VALID_CA;
+	    }
+	    
+	    if ( certtype & NS_CERT_TYPE_EMAIL_CA ) {
+		trust.emailFlags = CERTDB_VALID_CA;
+	    }
+	    
+	    if ( certtype & NS_CERT_TYPE_OBJECT_SIGNING_CA ) {
+		trust.objectSigningFlags = CERTDB_VALID_CA;
+	    }
+	    
+	} else {
+	    if ( certtype & NS_CERT_TYPE_SSL_CLIENT ) {
+		trust.sslFlags = CERTDB_VALID_PEER;
+	    }
+	    
+	    if ( certtype & NS_CERT_TYPE_EMAIL ) {
+		trust.emailFlags = CERTDB_VALID_PEER;
+	    }
+	    
+	    if ( certtype & NS_CERT_TYPE_OBJECT_SIGNING ) {
+		trust.objectSigningFlags = CERTDB_VALID_PEER;
+	    }
+	}
+	break;
+      default:	/* XXX added to quiet warnings; no other cases needed? */
+	break;
+    }
+
+    if ( saveit ) {
+	if ( cert->isperm ) {
+	    /* Cert already in the DB.  Just adjust flags */
+	    tmptrust = *cert->trust;
+	    tmptrust.sslFlags |= trust.sslFlags;
+	    tmptrust.emailFlags |= trust.emailFlags;
+	    tmptrust.objectSigningFlags |= trust.objectSigningFlags;
+	    
+	    rv = CERT_ChangeCertTrust(cert->dbhandle, cert,
+				      &tmptrust);
+	    if ( rv != SECSuccess ) {
+		goto loser;
+	    }
+	} else {
+	    /* Cert not already in the DB.  Add it */
+	    rv = CERT_AddTempCertToPerm(cert, nickname, &trust);
+	    if ( rv != SECSuccess ) {
+		goto loser;
+	    }
+	}
+    }
+
+    rv = SECSuccess;
+    goto done;
+
+loser:
+    rv = SECFailure;
+done:
+
+    if ( freeNickname ) {
+	PORT_Free(nickname);
+    }
+    
+    return(rv);
+}
+
+SECStatus
+CERT_ChangeCertTrustByUsage(CERTCertDBHandle *certdb,
+			    CERTCertificate *cert, SECCertUsage usage)
+{
+    SECStatus rv;
+    CERTCertTrust trust;
+    CERTCertTrust tmptrust;
+    unsigned int certtype;
+    PRBool saveit;
+    
+    saveit = PR_TRUE;
+    
+    PORT_Memset((void *)&trust, 0, sizeof(trust));
+
+    certtype = cert->nsCertType;
+
+    /* if no app bits in cert type, then set all app bits */
+    if ( ! ( certtype & NS_CERT_TYPE_APP ) ) {
+	certtype |= NS_CERT_TYPE_APP;
+    }
+
+    switch ( usage ) {
+      case certUsageEmailSigner:
+      case certUsageEmailRecipient:
+	if ( certtype & NS_CERT_TYPE_EMAIL ) {
+	     trust.emailFlags = CERTDB_VALID_PEER;
+	     if ( ! ( cert->rawKeyUsage & KU_KEY_ENCIPHERMENT ) ) {
+		/* don't save it if KeyEncipherment is not allowed */
+		saveit = PR_FALSE;
+	    }
+	}
+	break;
+      case certUsageUserCertImport:
+	if ( certtype & NS_CERT_TYPE_EMAIL ) {
+	    trust.emailFlags = CERTDB_VALID_PEER;
+	}
+	/* VALID_USER is already set if the cert was imported, 
+	 * in the case that the cert was already in the database
+	 * through SMIME or other means, we should set the USER
+	 * flags, if they are not already set.
+	 */
+	if( cert->isperm ) {
+	    if ( certtype & NS_CERT_TYPE_SSL_CLIENT ) {
+		if( !(cert->trust->sslFlags & CERTDB_USER) ) {
+		    trust.sslFlags |= CERTDB_USER;
+		}
+	    }
+	    
+	    if ( certtype & NS_CERT_TYPE_EMAIL ) {
+		if( !(cert->trust->emailFlags & CERTDB_USER) ) {
+		    trust.emailFlags |= CERTDB_USER;
+		}
+	    }
+	    
+	    if ( certtype & NS_CERT_TYPE_OBJECT_SIGNING ) {
+		if( !(cert->trust->objectSigningFlags & CERTDB_USER) ) {
+		    trust.objectSigningFlags |= CERTDB_USER;
+		}
+	    }
+	}
+	break;
+      default:	/* XXX added to quiet warnings; no other cases needed? */
+	break;
+    }
+
+    if ( (trust.sslFlags | trust.emailFlags | trust.objectSigningFlags) == 0 ){
+	saveit = PR_FALSE;
+    }
+
+    if ( saveit && cert->isperm ) {
+	/* Cert already in the DB.  Just adjust flags */
+	tmptrust = *cert->trust;
+	tmptrust.sslFlags |= trust.sslFlags;
+	tmptrust.emailFlags |= trust.emailFlags;
+	tmptrust.objectSigningFlags |= trust.objectSigningFlags;
+	    
+	rv = CERT_ChangeCertTrust(cert->dbhandle, cert,
+				  &tmptrust);
+	if ( rv != SECSuccess ) {
+	    goto loser;
+	}
+    }
+
+    rv = SECSuccess;
+    goto done;
+
+loser:
+    rv = SECFailure;
+done:
+
+    return(rv);
+}
+
+int
+CERT_GetDBContentVersion(CERTCertDBHandle *handle)
+{
+    certDBEntryContentVersion *entry;
+    int ret;
+    
+    entry = ReadDBContentVersionEntry(handle);
+    
+    if ( entry == NULL ) {
+	return(0);
+    }
+
+    ret = entry->contentVersion;
+
+    DestroyDBEntry((certDBEntry *)entry);
+
+    return(ret);
+}
+
+void
+CERT_SetDBContentVersion(int version, CERTCertDBHandle *handle)
+{
+    SECStatus rv;
+    certDBEntryContentVersion *entry;
+    
+    entry = NewDBContentVersionEntry(0);
+    
+    if ( entry == NULL ) {
+	return;
+    }
+    
+    rv = DeleteDBContentVersionEntry(handle);
+    rv = WriteDBContentVersionEntry(handle, entry);
+    
+    DestroyDBEntry((certDBEntry *)entry);
+    
+    return;
+}
+
+/*
+ * Creates or adds to a list of all certs with a give subject name, sorted by
+ * validity time, newest first.  Invalid certs are considered older than
+ * valid certs. If validOnly is set, do not include invalid certs on list.
+ */
+CERTCertList *
+CERT_CreateSubjectCertList(CERTCertList *certList, CERTCertDBHandle *handle,
+			   SECItem *name, int64 sorttime, PRBool validOnly)
+{
+    CERTCertificate *cert = NULL;
+    CERTSubjectList *subjectList = NULL;
+    CERTSubjectNode *node;
+    SECStatus rv;
+
+    if ( certList == NULL ) {
+	certList = CERT_NewCertList();
+    }
+    
+    if ( certList == NULL ) {
+	goto loser;
+    }
+    
+    subjectList = FindSubjectList(handle, name, PR_FALSE);
+
+    if ( subjectList != NULL ) {
+	node = subjectList->head;
+	PORT_Assert(node);
+	while (node) {
+	    cert = CERT_FindCertByKey(handle, &node->certKey);
+
+	    /* if validOnly, then check validity period before adding to list*/
+	    if ( ( !validOnly ) ||
+		( CERT_CheckCertValidTimes(cert, sorttime, PR_FALSE)
+		    == secCertTimeValid ) ) {
+		rv = CERT_AddCertToListSorted(certList, cert,
+					      CERT_SortCBValidity,
+					      (void *)&sorttime);
+		if ( rv != SECSuccess ) {
+		    CERT_DestroyCertificate(cert);
+		    goto loser;
+		}
+	    } else {
+		CERT_DestroyCertificate(cert);
+	    }
+
+	    node = node->next;
+	}
+    }
+
+    return(certList);
+
+loser:
+    if ( certList != NULL ) {
+	CERT_DestroyCertList(certList);
+    }
+    
+    return(NULL);
+}
+
+/*
+ * Creates or adds to a list of all certs with a give nickname, sorted by
+ * validity time, newest first.  Invalid certs are considered older than valid
+ * certs. If validOnly is set, do not include invalid certs on list.
+ */
+CERTCertList *
+CERT_CreateNicknameCertList(CERTCertList *certList, CERTCertDBHandle *handle,
+			    char *nickname, int64 sorttime, PRBool validOnly)
+{
+    CERTCertificate *cert;
+    CERTCertList *ret;
+    
+    cert = CERT_FindCertByNickname(handle, nickname);
+    if ( cert == NULL ) {
+	return(NULL);
+    }
+    
+    ret = CERT_CreateSubjectCertList(certList, handle, &cert->derSubject,
+				     sorttime, validOnly);
+
+    CERT_DestroyCertificate(cert);
+    
+    return(ret);
+}
+
+/*
+ * Creates or adds to a list of all certs with a give email addr, sorted by
+ * validity time, newest first.  Invalid certs are considered older than valid
+ * certs. If validOnly is set, do not include invalid certs on list.
+ */
+CERTCertList *
+CERT_CreateEmailAddrCertList(CERTCertList *certList, CERTCertDBHandle *handle,
+			     char *emailAddr, int64 sorttime, PRBool validOnly)
+{
+    CERTCertificate *cert;
+    CERTCertList *ret;
+    
+    cert = CERT_FindCertByEmailAddr(handle, emailAddr);
+    if ( cert == NULL ) {
+	return(NULL);
+    }
+    
+    ret = CERT_CreateSubjectCertList(certList, handle, &cert->derSubject,
+				     sorttime, validOnly);
+
+    CERT_DestroyCertificate(cert);
+    
+    return(ret);
+}
new file mode 100644
--- /dev/null
+++ b/security/nss/lib/ckfw/object.c
@@ -0,0 +1,1044 @@
+/* 
+ * The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ * 
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ * 
+ * The Original Code is the Netscape security libraries.
+ * 
+ * The Initial Developer of the Original Code is Netscape
+ * Communications Corporation.  Portions created by Netscape are 
+ * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
+ * Rights Reserved.
+ * 
+ * Contributor(s):
+ * 
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU General Public License Version 2 or later (the
+ * "GPL"), in which case the provisions of the GPL are applicable 
+ * instead of those above.  If you wish to allow use of your 
+ * version of this file only under the terms of the GPL and not to
+ * allow others to use your version of this file under the MPL,
+ * indicate your decision by deleting the provisions above and
+ * replace them with the notice and other provisions required by
+ * the GPL.  If you do not delete the provisions above, a recipient
+ * may use your version of this file under either the MPL or the
+ * GPL.
+ */
+
+#ifdef DEBUG
+static const char CVS_ID[] = "@(#) $RCSfile$ $Revision$ $Date$ $Name$";
+#endif /* DEBUG */
+
+/*
+ * object.c
+ *
+ * This file implements the NSSCKFWObject type and methods.
+ */
+
+#ifndef CK_T
+#include "ck.h"
+#endif /* CK_T */
+
+/*
+ * NSSCKFWObject
+ *
+ * -- create/destroy --
+ *  nssCKFWObject_Create
+ *  nssCKFWObject_Finalize
+ *  nssCKFWObject_Destroy
+ *
+ * -- public accessors --
+ *  NSSCKFWObject_GetMDObject
+ *  NSSCKFWObject_GetArena
+ *  NSSCKFWObject_IsTokenObject
+ *  NSSCKFWObject_GetAttributeCount
+ *  NSSCKFWObject_GetAttributeTypes
+ *  NSSCKFWObject_GetAttributeSize
+ *  NSSCKFWObject_GetAttribute
+ *  NSSCKFWObject_SetAttribute
+ *  NSSCKFWObject_GetObjectSize
+ *
+ * -- implement public accessors --
+ *  nssCKFWObject_GetMDObject
+ *  nssCKFWObject_GetArena
+ *
+ * -- private accessors --
+ *  nssCKFWObject_SetHandle
+ *  nssCKFWObject_GetHandle
+ *
+ * -- module fronts --
+ *  nssCKFWObject_IsTokenObject
+ *  nssCKFWObject_GetAttributeCount
+ *  nssCKFWObject_GetAttributeTypes
+ *  nssCKFWObject_GetAttributeSize
+ *  nssCKFWObject_GetAttribute
+ *  nssCKFWObject_SetAttribute
+ *  nssCKFWObject_GetObjectSize
+ */
+
+struct NSSCKFWObjectStr {
+  NSSCKFWMutex *mutex; /* merely to serialise the MDObject calls */
+  NSSArena *arena;
+  NSSCKMDObject *mdObject;
+  NSSCKMDSession *mdSession;
+  NSSCKFWSession *fwSession;
+  NSSCKMDToken *mdToken;
+  NSSCKFWToken *fwToken;
+  NSSCKMDInstance *mdInstance;
+  NSSCKFWInstance *fwInstance;
+  CK_OBJECT_HANDLE hObject;
+};
+
+#ifdef DEBUG
+/*
+ * But first, the pointer-tracking stuff.
+ *
+ * NOTE: the pointer-tracking support in NSS/base currently relies
+ * upon NSPR's CallOnce support.  That, however, relies upon NSPR's
+ * locking, which is tied into the runtime.  We need a pointer-tracker
+ * implementation that uses the locks supplied through C_Initialize.
+ * That support, however, can be filled in later.  So for now, I'll
+ * just do this routines as no-ops.
+ */
+
+static CK_RV
+object_add_pointer
+(
+  const NSSCKFWObject *fwObject
+)
+{
+  return CKR_OK;
+}
+
+static CK_RV
+object_remove_pointer
+(
+  const NSSCKFWObject *fwObject
+)
+{
+  return CKR_OK;
+}
+
+NSS_IMPLEMENT CK_RV
+nssCKFWObject_verifyPointer
+(
+  const NSSCKFWObject *fwObject
+)
+{
+  return CKR_OK;
+}
+
+#endif /* DEBUG */
+
+
+/*
+ * nssCKFWObject_Create
+ *
+ */
+NSS_IMPLEMENT NSSCKFWObject *
+nssCKFWObject_Create
+(
+  NSSArena *arena,
+  NSSCKMDObject *mdObject,
+  NSSCKFWSession *fwSession,
+  NSSCKFWToken *fwToken,
+  NSSCKFWInstance *fwInstance,
+  CK_RV *pError
+)
+{
+  NSSCKFWObject *fwObject;
+  nssCKFWHash *mdObjectHash;
+
+#ifdef NSSDEBUG
+  if( (CK_RV *)NULL == pError ) {
+    return (NSSCKFWObject *)NULL;
+  }
+
+  if( PR_SUCCESS != nssArena_verifyPointer(arena) ) {
+    *pError = CKR_ARGUMENTS_BAD;
+    return (NSSCKFWObject *)NULL;
+  }
+#endif /* NSSDEBUG */
+
+  mdObjectHash = nssCKFWToken_GetMDObjectHash(fwToken);
+  if( (nssCKFWHash *)NULL == mdObjectHash ) {
+    *pError = CKR_GENERAL_ERROR;
+    return (NSSCKFWObject *)NULL;
+  }
+
+  if( nssCKFWHash_Exists(mdObjectHash, mdObject) ) {
+    fwObject = nssCKFWHash_Lookup(mdObjectHash, mdObject);
+    return fwObject;
+  }
+
+  fwObject = nss_ZNEW(arena, NSSCKFWObject);
+  if( (NSSCKFWObject *)NULL == fwObject ) {
+    *pError = CKR_HOST_MEMORY;
+    return (NSSCKFWObject *)NULL;
+  }
+
+  fwObject->arena = arena;
+  fwObject->mdObject = mdObject;
+  fwObject->fwSession = fwSession;
+
+  if( (NSSCKFWSession *)NULL != fwSession ) {
+    fwObject->mdSession = nssCKFWSession_GetMDSession(fwSession);
+  }
+
+  fwObject->fwToken = fwToken;
+
+  if( (NSSCKFWToken *)NULL != fwToken ) {
+    fwObject->mdToken = nssCKFWToken_GetMDToken(fwToken);
+  }
+
+  fwObject->fwInstance = fwInstance;
+  fwObject->mdInstance = nssCKFWInstance_GetMDInstance(fwInstance);
+  fwObject->mutex = nssCKFWInstance_CreateMutex(fwInstance, arena, pError);
+  if( (NSSCKFWMutex *)NULL == fwObject->mutex ) {
+    if( CKR_OK == *pError ) {
+      *pError = CKR_GENERAL_ERROR;
+    }
+    return (NSSCKFWObject *)NULL;
+  }
+
+  *pError = nssCKFWHash_Add(mdObjectHash, mdObject, fwObject);
+  if( CKR_OK != *pError ) {
+    nss_ZFreeIf(fwObject);
+    return (NSSCKFWObject *)NULL;
+  }
+
+#ifdef DEBUG
+  *pError = object_add_pointer(fwObject);
+  if( CKR_OK != *pError ) {
+    nssCKFWHash_Remove(mdObjectHash, mdObject);
+    nss_ZFreeIf(fwObject);
+    return (NSSCKFWObject *)NULL;
+  }
+#endif /* DEBUG */
+
+  *pError = CKR_OK;
+  return fwObject;
+}
+
+/*
+ * nssCKFWObject_Finalize
+ *
+ */
+NSS_IMPLEMENT void
+nssCKFWObject_Finalize
+(
+  NSSCKFWObject *fwObject
+)
+{
+  nssCKFWHash *mdObjectHash;
+
+#ifdef NSSDEBUG
+  if( CKR_OK != nssCKFWObject_verifyPointer(fwObject) ) {
+    return;
+  }
+#endif /* NSSDEBUG */
+
+  (void)nssCKFWMutex_Destroy(fwObject->mutex);
+
+  if( (void *)NULL != (void *)fwObject->mdObject->Finalize ) {
+    fwObject->mdObject->Finalize(fwObject->mdObject, fwObject,
+      fwObject->mdSession, fwObject->fwSession, fwObject->mdToken,
+      fwObject->fwToken, fwObject->mdInstance, fwObject->fwInstance);
+  }
+
+  mdObjectHash = nssCKFWToken_GetMDObjectHash(fwObject->fwToken);
+  if( (nssCKFWHash *)NULL != mdObjectHash ) {
+    nssCKFWHash_Remove(mdObjectHash, fwObject->mdObject);
+  }
+
+  nssCKFWSession_DeregisterSessionObject(fwObject->fwSession, fwObject);
+  nss_ZFreeIf(fwObject);
+
+#ifdef DEBUG
+  (void)object_remove_pointer(fwObject);
+#endif /* DEBUG */
+
+  return;
+}
+
+/*
+ * nssCKFWObject_Destroy
+ *
+ */
+NSS_IMPLEMENT void
+nssCKFWObject_Destroy
+(
+  NSSCKFWObject *fwObject
+)
+{
+  nssCKFWHash *mdObjectHash;
+
+#ifdef NSSDEBUG
+  if( CKR_OK != nssCKFWObject_verifyPointer(fwObject) ) {
+    return;
+  }
+#endif /* NSSDEBUG */
+
+  (void)nssCKFWMutex_Destroy(fwObject->mutex);
+
+  if( (void *)NULL != (void *)fwObject->mdObject->Destroy ) {
+    fwObject->mdObject->Destroy(fwObject->mdObject, fwObject,
+      fwObject->mdSession, fwObject->fwSession, fwObject->mdToken,
+      fwObject->fwToken, fwObject->mdInstance, fwObject->fwInstance);
+  }
+
+  mdObjectHash = nssCKFWToken_GetMDObjectHash(fwObject->fwToken);
+  if( (nssCKFWHash *)NULL != mdObjectHash ) {
+    nssCKFWHash_Remove(mdObjectHash, fwObject->mdObject);
+  }
+
+  nssCKFWSession_DeregisterSessionObject(fwObject->fwSession, fwObject);
+  nss_ZFreeIf(fwObject);
+
+#ifdef DEBUG
+  (void)object_remove_pointer(fwObject);
+#endif /* DEBUG */
+
+  return;
+}
+
+/*
+ * nssCKFWObject_GetMDObject
+ *
+ */
+NSS_IMPLEMENT NSSCKMDObject *
+nssCKFWObject_GetMDObject
+(
+  NSSCKFWObject *fwObject
+)
+{
+#ifdef NSSDEBUG
+  if( CKR_OK != nssCKFWObject_verifyPointer(fwObject) ) {
+    return (NSSCKMDObject *)NULL;
+  }
+#endif /* NSSDEBUG */
+
+  return fwObject->mdObject;
+}
+
+/*
+ * nssCKFWObject_GetArena
+ *
+ */
+NSS_IMPLEMENT NSSArena *
+nssCKFWObject_GetArena
+(
+  NSSCKFWObject *fwObject,
+  CK_RV *pError
+)
+{
+#ifdef NSSDEBUG
+  if( (CK_RV *)NULL == pError ) {
+    return (NSSArena *)NULL;
+  }
+
+  *pError = nssCKFWObject_verifyPointer(fwObject);
+  if( CKR_OK != *pError ) {
+    return (NSSArena *)NULL;
+  }
+#endif /* NSSDEBUG */
+
+  return fwObject->arena;
+}
+
+/*
+ * nssCKFWObject_SetHandle
+ *
+ */
+NSS_IMPLEMENT CK_RV
+nssCKFWObject_SetHandle
+(
+  NSSCKFWObject *fwObject,
+  CK_OBJECT_HANDLE hObject
+)
+{
+#ifdef NSSDEBUG
+  CK_RV error = CKR_OK;
+#endif /* NSSDEBUG */
+
+#ifdef NSSDEBUG
+  error = nssCKFWObject_verifyPointer(fwObject);
+  if( CKR_OK != error ) {
+    return error;
+  }
+#endif /* NSSDEBUG */
+
+  if( (CK_OBJECT_HANDLE)0 != fwObject->hObject ) {
+    return CKR_GENERAL_ERROR;
+  }
+
+  fwObject->hObject = hObject;
+
+  return CKR_OK;
+}
+
+/*
+ * nssCKFWObject_GetHandle
+ *
+ */
+NSS_IMPLEMENT CK_OBJECT_HANDLE
+nssCKFWObject_GetHandle
+(
+  NSSCKFWObject *fwObject
+)
+{
+#ifdef NSSDEBUG
+  if( CKR_OK != nssCKFWObject_verifyPointer(fwObject) ) {
+    return (CK_OBJECT_HANDLE)0;
+  }
+#endif /* NSSDEBUG */
+
+  return fwObject->hObject;
+}
+
+/*
+ * nssCKFWObject_IsTokenObject
+ *
+ */
+NSS_IMPLEMENT CK_BBOOL
+nssCKFWObject_IsTokenObject
+(
+  NSSCKFWObject *fwObject
+)
+{
+  CK_BBOOL b = CK_FALSE;
+
+#ifdef NSSDEBUG
+  if( CKR_OK != nssCKFWObject_verifyPointer(fwObject) ) {
+    return CK_FALSE;
+  }
+#endif /* NSSDEBUG */
+
+  if( CKR_OK != nssCKFWMutex_Lock(fwObject->mutex) ) {
+    return CK_FALSE;
+  }
+
+  if( (void *)NULL == (void *)fwObject->mdObject->IsTokenObject ) {
+    NSSItem item;
+    NSSItem *pItem;
+    CK_RV rv = CKR_OK;
+
+    item.data = (void *)&b;
+    item.size = sizeof(b);
+
+    pItem = nssCKFWObject_GetAttribute(fwObject, CKA_TOKEN, &item, 
+      (NSSArena *)NULL, &rv);
+    if( (NSSItem *)NULL == pItem ) {
+      /* Error of some type */
+      b = CK_FALSE;
+      goto done;
+    }
+
+    goto done;
+  }
+
+  b = fwObject->mdObject->IsTokenObject(fwObject->mdObject, fwObject, 
+    fwObject->mdSession, fwObject->fwSession, fwObject->mdToken,
+    fwObject->fwToken, fwObject->mdInstance, fwObject->fwInstance);
+
+ done:
+  (void)nssCKFWMutex_Unlock(fwObject->mutex);
+  return b;
+}
+
+/*
+ * nssCKFWObject_GetAttributeCount
+ *
+ */
+NSS_IMPLEMENT CK_ULONG
+nssCKFWObject_GetAttributeCount
+(
+  NSSCKFWObject *fwObject,
+  CK_RV *pError
+)
+{
+  CK_ULONG rv;
+
+#ifdef NSSDEBUG
+  if( (CK_RV *)NULL == pError ) {
+    return (CK_ULONG)0;
+  }
+
+  *pError = nssCKFWObject_verifyPointer(fwObject);
+  if( CKR_OK != *pError ) {
+    return (CK_ULONG)0;
+  }
+#endif /* NSSDEBUG */
+
+  if( (void *)NULL == (void *)fwObject->mdObject->GetAttributeCount ) {
+    *pError = CKR_GENERAL_ERROR;
+    return (CK_ULONG)0;
+  }
+
+  *pError = nssCKFWMutex_Lock(fwObject->mutex);
+  if( CKR_OK != *pError ) {
+    return (CK_ULONG)0;
+  }
+
+  rv = fwObject->mdObject->GetAttributeCount(fwObject->mdObject, fwObject,
+    fwObject->mdSession, fwObject->fwSession, fwObject->mdToken, 
+    fwObject->fwToken, fwObject->mdInstance, fwObject->fwInstance,
+    pError);
+
+  (void)nssCKFWMutex_Unlock(fwObject->mutex);
+  return rv;
+}
+
+/*
+ * nssCKFWObject_GetAttributeTypes
+ *
+ */
+NSS_IMPLEMENT CK_RV
+nssCKFWObject_GetAttributeTypes
+(
+  NSSCKFWObject *fwObject,
+  CK_ATTRIBUTE_TYPE_PTR typeArray,
+  CK_ULONG ulCount
+)
+{
+  CK_RV error = CKR_OK;
+
+#ifdef NSSDEBUG
+  error = nssCKFWObject_verifyPointer(fwObject);
+  if( CKR_OK != error ) {
+    return error;
+  }
+
+  if( (CK_ATTRIBUTE_TYPE_PTR)NULL == typeArray ) {
+    return CKR_ARGUMENTS_BAD;
+  }
+#endif /* NSSDEBUG */
+
+  if( (void *)NULL == (void *)fwObject->mdObject->GetAttributeTypes ) {
+    return CKR_GENERAL_ERROR;
+  }
+
+  error = nssCKFWMutex_Lock(fwObject->mutex);
+  if( CKR_OK != error ) {
+    return error;
+  }
+
+  error = fwObject->mdObject->GetAttributeTypes(fwObject->mdObject, fwObject,
+    fwObject->mdSession, fwObject->fwSession, fwObject->mdToken, 
+    fwObject->fwToken, fwObject->mdInstance, fwObject->fwInstance,
+    typeArray, ulCount);
+
+  (void)nssCKFWMutex_Unlock(fwObject->mutex);
+  return error;
+}
+
+/*
+ * nssCKFWObject_GetAttributeSize
+ *
+ */
+NSS_IMPLEMENT CK_ULONG
+nssCKFWObject_GetAttributeSize
+(
+  NSSCKFWObject *fwObject,
+  CK_ATTRIBUTE_TYPE attribute,
+  CK_RV *pError
+)
+{
+  CK_ULONG rv;
+
+#ifdef NSSDEBUG
+  if( (CK_RV *)NULL == pError ) {
+    return (CK_ULONG)0;
+  }
+
+  *pError = nssCKFWObject_verifyPointer(fwObject);
+  if( CKR_OK != *pError ) {
+    return (CK_ULONG)0;
+  }
+#endif /* NSSDEBUG */
+
+  if( (void *)NULL == (void *)fwObject->mdObject->GetAttributeSize ) {
+    *pError = CKR_GENERAL_ERROR;
+    return (CK_ULONG )0;
+  }
+
+  *pError = nssCKFWMutex_Lock(fwObject->mutex);
+  if( CKR_OK != *pError ) {
+    return (CK_ULONG)0;
+  }
+
+  rv = fwObject->mdObject->GetAttributeSize(fwObject->mdObject, fwObject,
+    fwObject->mdSession, fwObject->fwSession, fwObject->mdToken, 
+    fwObject->fwToken, fwObject->mdInstance, fwObject->fwInstance,
+    attribute, pError);
+
+  (void)nssCKFWMutex_Unlock(fwObject->mutex);
+  return rv;
+}
+
+/*
+ * nssCKFWObject_GetAttribute
+ *
+ * Usual NSS allocation rules:
+ * If itemOpt is not NULL, it will be returned; otherwise an NSSItem
+ * will be allocated.  If itemOpt is not NULL but itemOpt->data is,
+ * the buffer will be allocated; otherwise, the buffer will be used.
+ * Any allocations will come from the optional arena, if one is
+ * specified.
+ */
+NSS_IMPLEMENT NSSItem *
+nssCKFWObject_GetAttribute
+(
+  NSSCKFWObject *fwObject,
+  CK_ATTRIBUTE_TYPE attribute,
+  NSSItem *itemOpt,
+  NSSArena *arenaOpt,
+  CK_RV *pError
+)
+{
+  NSSItem *rv = (NSSItem *)NULL;
+  NSSItem *mdItem;
+
+#ifdef NSSDEBUG
+  if( (CK_RV *)NULL == pError ) {
+    return (NSSItem *)NULL;
+  }
+
+  *pError = nssCKFWObject_verifyPointer(fwObject);
+  if( CKR_OK != *pError ) {
+    return (NSSItem *)NULL;
+  }
+#endif /* NSSDEBUG */
+
+  if( (void *)NULL == (void *)fwObject->mdObject->GetAttributeSize ) {
+    *pError = CKR_GENERAL_ERROR;
+    return (NSSItem *)NULL;
+  }
+
+  *pError = nssCKFWMutex_Lock(fwObject->mutex);
+  if( CKR_OK != *pError ) {
+    return (NSSItem *)NULL;
+  }
+
+  mdItem = fwObject->mdObject->GetAttribute(fwObject->mdObject, fwObject,
+    fwObject->mdSession, fwObject->fwSession, fwObject->mdToken, 
+    fwObject->fwToken, fwObject->mdInstance, fwObject->fwInstance,
+    attribute, pError);
+
+  if( (NSSItem *)NULL == mdItem ) {
+    if( CKR_OK == *pError ) {
+      *pError = CKR_GENERAL_ERROR;
+    }
+
+    goto done;
+  }
+
+  if( (NSSItem *)NULL == itemOpt ) {
+    rv = nss_ZNEW(arenaOpt, NSSItem);
+    if( (NSSItem *)NULL == rv ) {
+      *pError = CKR_HOST_MEMORY;
+      goto done;
+    }
+  } else {
+    rv = itemOpt;
+  }
+
+  if( (void *)NULL == rv->data ) {
+    rv->size = mdItem->size;
+    rv->data = nss_ZAlloc(arenaOpt, rv->size);
+    if( (void *)NULL == rv->data ) {
+      *pError = CKR_HOST_MEMORY;
+      if( (NSSItem *)NULL == itemOpt ) {
+        nss_ZFreeIf(rv);
+      }
+      rv = (NSSItem *)NULL;
+      goto done;
+    }
+  } else {
+    if( rv->size >= mdItem->size ) {
+      rv->size = mdItem->size;
+    } else {
+      *pError = CKR_BUFFER_TOO_SMALL;
+      /* Should we set rv->size to mdItem->size? */
+      /* rv can't have been allocated */
+      rv = (NSSItem *)NULL;
+      goto done;
+    }
+  }
+
+  (void)nsslibc_memcpy(rv->data, mdItem->data, rv->size);
+
+ done:
+  (void)nssCKFWMutex_Unlock(fwObject->mutex);
+  return rv;
+}
+
+/*
+ * nssCKFWObject_SetAttribute
+ *
+ */
+NSS_IMPLEMENT CK_RV
+nssCKFWObject_SetAttribute
+(
+  NSSCKFWObject *fwObject,
+  CK_ATTRIBUTE_TYPE attribute,
+  NSSItem *value
+)
+{
+  CK_RV error = CKR_OK;
+
+#ifdef NSSDEBUG
+  error = nssCKFWObject_verifyPointer(fwObject);
+  if( CKR_OK != error ) {
+    return error;
+  }
+#endif /* NSSDEBUG */
+
+  if( CKA_TOKEN == attribute ) {
+    /*
+     * We're changing from a session object to a token object or 
+     * vice-versa.
+     */
+
+    CK_ATTRIBUTE a;
+    NSSCKFWObject *newFwObject;
+    NSSCKFWObject swab;
+
+    a.type = CKA_TOKEN;
+    a.pValue = value->data;
+    a.ulValueLen = value->size;
+
+    newFwObject = nssCKFWSession_CopyObject(fwObject->fwSession, fwObject,
+                    &a, 1, &error);
+    if( (NSSCKFWObject *)NULL == newFwObject ) {
+      if( CKR_OK == error ) {
+        error = CKR_GENERAL_ERROR;
+      }
+      return error;
+    }
+
+    /*
+     * Actually, I bet the locking is worse than this.. this part of
+     * the code could probably use some scrutiny and reworking.
+     */
+    error = nssCKFWMutex_Lock(fwObject->mutex);
+    if( CKR_OK != error ) {
+      nssCKFWObject_Destroy(newFwObject);
+      return error;
+    }
+
+    error = nssCKFWMutex_Lock(newFwObject->mutex);
+    if( CKR_OK != error ) {
+      nssCKFWMutex_Unlock(fwObject->mutex);
+      nssCKFWObject_Destroy(newFwObject);
+      return error;
+    }
+
+    /* 
+     * Now, we have our new object, but it has a new fwObject pointer,
+     * while we have to keep the existing one.  So quick swap the contents.
+     */
+    swab = *fwObject;
+    *fwObject = *newFwObject;
+    *newFwObject = swab;
+
+    /* But keep the mutexes the same */
+    swab.mutex = fwObject->mutex;
+    fwObject->mutex = newFwObject->mutex;
+    newFwObject->mutex = swab.mutex;
+
+    (void)nssCKFWMutex_Unlock(newFwObject->mutex);
+    (void)nssCKFWMutex_Unlock(fwObject->mutex);
+
+    /*
+     * Either remove or add this to the list of session objects
+     */
+
+    if( CK_FALSE == *(CK_BBOOL *)value->data ) {
+      /* 
+       * New one is a session object, except since we "stole" the fwObject, it's
+       * not in the list.  Add it.
+       */
+      nssCKFWSession_RegisterSessionObject(fwObject->fwSession, fwObject);
+    } else {
+      /*
+       * New one is a token object, except since we "stole" the fwObject, it's
+       * in the list.  Remove it.
+       */
+      nssCKFWSession_DeregisterSessionObject(fwObject->fwSession, fwObject);
+    }
+
+    /*
+     * Now delete the old object.  Remember the names have changed.
+     */
+    nssCKFWObject_Destroy(newFwObject);
+
+    return CKR_OK;
+  } else {
+    /*
+     * An "ordinary" change.
+     */
+    if( (void *)NULL == (void *)fwObject->mdObject->SetAttribute ) {
+      /* We could fake it with copying, like above.. later */
+      return CKR_ATTRIBUTE_READ_ONLY;
+    }
+
+    error = nssCKFWMutex_Lock(fwObject->mutex);
+    if( CKR_OK != error ) {
+      return error;
+    }
+
+    error = fwObject->mdObject->SetAttribute(fwObject->mdObject, fwObject,
+      fwObject->mdSession, fwObject->fwSession, fwObject->mdToken, 
+      fwObject->fwToken, fwObject->mdInstance, fwObject->fwInstance,
+      attribute, value);
+
+    (void)nssCKFWMutex_Unlock(fwObject->mutex);
+
+    return error;
+  }
+}
+
+/*
+ * nssCKFWObject_GetObjectSize
+ *
+ */
+NSS_IMPLEMENT CK_ULONG
+nssCKFWObject_GetObjectSize
+(
+  NSSCKFWObject *fwObject,
+  CK_RV *pError
+)
+{
+  CK_ULONG rv;
+
+#ifdef NSSDEBUG
+  if( (CK_RV *)NULL == pError ) {
+    return (CK_ULONG)0;
+  }
+
+  *pError = nssCKFWObject_verifyPointer(fwObject);
+  if( CKR_OK != *pError ) {
+    return (CK_ULONG)0;
+  }
+#endif /* NSSDEBUG */
+
+  if( (void *)NULL == (void *)fwObject->mdObject->GetObjectSize ) {
+    *pError = CKR_INFORMATION_SENSITIVE;
+    return (CK_ULONG)0;
+  }
+
+  *pError = nssCKFWMutex_Lock(fwObject->mutex);
+  if( CKR_OK != *pError ) {
+    return (CK_ULONG)0;
+  }
+
+  rv = fwObject->mdObject->GetObjectSize(fwObject->mdObject, fwObject,
+    fwObject->mdSession, fwObject->fwSession, fwObject->mdToken, 
+    fwObject->fwToken, fwObject->mdInstance, fwObject->fwInstance,
+    pError);
+
+  (void)nssCKFWMutex_Unlock(fwObject->mutex);
+  return rv;
+}
+
+/*
+ * NSSCKFWObject_GetMDObject
+ *
+ */
+NSS_IMPLEMENT NSSCKMDObject *
+NSSCKFWObject_GetMDObject
+(
+  NSSCKFWObject *fwObject
+)
+{
+#ifdef DEBUG
+  if( CKR_OK != nssCKFWObject_verifyPointer(fwObject) ) {
+    return (NSSCKMDObject *)NULL;
+  }
+#endif /* DEBUG */
+
+  return nssCKFWObject_GetMDObject(fwObject);
+}
+
+/*
+ * NSSCKFWObject_GetArena
+ *
+ */
+NSS_IMPLEMENT NSSArena *
+NSSCKFWObject_GetArena
+(
+  NSSCKFWObject *fwObject,
+  CK_RV *pError
+)
+{
+#ifdef DEBUG
+  if( (CK_RV *)NULL == pError ) {
+    return (NSSArena *)NULL;
+  }
+
+  *pError = nssCKFWObject_verifyPointer(fwObject);
+  if( CKR_OK != *pError ) {
+    return (NSSArena *)NULL;
+  }
+#endif /* DEBUG */
+
+  return nssCKFWObject_GetArena(fwObject, pError);
+}
+
+/*
+ * NSSCKFWObject_IsTokenObject
+ *
+ */
+NSS_IMPLEMENT CK_BBOOL
+NSSCKFWObject_IsTokenObject
+(
+  NSSCKFWObject *fwObject
+)
+{
+#ifdef DEBUG
+  if( CKR_OK != nssCKFWObject_verifyPointer(fwObject) ) {
+    return CK_FALSE;
+  }
+#endif /* DEBUG */
+
+  return nssCKFWObject_IsTokenObject(fwObject);
+}
+
+/*
+ * NSSCKFWObject_GetAttributeCount
+ *
+ */
+NSS_IMPLEMENT CK_ULONG
+NSSCKFWObject_GetAttributeCount
+(
+  NSSCKFWObject *fwObject,
+  CK_RV *pError
+)
+{
+#ifdef DEBUG
+  if( (CK_RV *)NULL == pError ) {
+    return (CK_ULONG)0;
+  }
+
+  *pError = nssCKFWObject_verifyPointer(fwObject);
+  if( CKR_OK != *pError ) {
+    return (CK_ULONG)0;
+  }
+#endif /* DEBUG */
+
+  return nssCKFWObject_GetAttributeCount(fwObject, pError);
+}
+
+/*
+ * NSSCKFWObject_GetAttributeTypes
+ *
+ */
+NSS_IMPLEMENT CK_RV
+NSSCKFWObject_GetAttributeTypes
+(
+  NSSCKFWObject *fwObject,
+  CK_ATTRIBUTE_TYPE_PTR typeArray,
+  CK_ULONG ulCount
+)
+{
+  CK_RV error = CKR_OK;
+
+#ifdef DEBUG
+  error = nssCKFWObject_verifyPointer(fwObject);
+  if( CKR_OK != error ) {
+    return error;
+  }
+
+  if( (CK_ATTRIBUTE_TYPE_PTR)NULL == typeArray ) {
+    return CKR_ARGUMENTS_BAD;
+  }
+#endif /* DEBUG */
+
+  return nssCKFWObject_GetAttributeTypes(fwObject, typeArray, ulCount);
+}
+
+/*
+ * NSSCKFWObject_GetAttributeSize
+ *
+ */
+NSS_IMPLEMENT CK_ULONG
+NSSCKFWObject_GetAttributeSize
+(
+  NSSCKFWObject *fwObject,
+  CK_ATTRIBUTE_TYPE attribute,
+  CK_RV *pError
+)
+{
+#ifdef DEBUG
+  if( (CK_RV *)NULL == pError ) {
+    return (CK_ULONG)0;
+  }
+
+  *pError = nssCKFWObject_verifyPointer(fwObject);
+  if( CKR_OK != *pError ) {
+    return (CK_ULONG)0;
+  }
+#endif /* DEBUG */
+
+  return nssCKFWObject_GetAttributeSize(fwObject, attribute, pError);
+}
+
+/*
+ * NSSCKFWObject_GetAttribute
+ *
+ */
+NSS_IMPLEMENT NSSItem *
+NSSCKFWObject_GetAttribute
+(
+  NSSCKFWObject *fwObject,
+  CK_ATTRIBUTE_TYPE attribute,
+  NSSItem *itemOpt,
+  NSSArena *arenaOpt,
+  CK_RV *pError
+)
+{
+#ifdef DEBUG
+  if( (CK_RV *)NULL == pError ) {
+    return (NSSItem *)NULL;
+  }
+
+  *pError = nssCKFWObject_verifyPointer(fwObject);
+  if( CKR_OK != *pError ) {
+    return (NSSItem *)NULL;
+  }
+#endif /* DEBUG */
+
+  return nssCKFWObject_GetAttribute(fwObject, attribute, itemOpt, arenaOpt, pError);
+}
+
+/*
+ * NSSCKFWObject_GetObjectSize
+ *
+ */
+NSS_IMPLEMENT CK_ULONG
+NSSCKFWObject_GetObjectSize
+(
+  NSSCKFWObject *fwObject,
+  CK_RV *pError
+)
+{
+#ifdef DEBUG
+  if( (CK_RV *)NULL == pError ) {
+    return (CK_ULONG)0;
+  }
+
+  *pError = nssCKFWObject_verifyPointer(fwObject);
+  if( CKR_OK != *pError ) {
+    return (CK_ULONG)0;
+  }
+#endif /* DEBUG */
+
+  return nssCKFWObject_GetObjectSize(fwObject, pError);
+}
new file mode 100644
--- /dev/null
+++ b/security/nss/lib/nss/nss.def
@@ -0,0 +1,546 @@
+;+#
+;+# The contents of this file are subject to the Mozilla Public
+;+# License Version 1.1 (the "License"); you may not use this file
+;+# except in compliance with the License. You may obtain a copy of
+;+# the License at http://www.mozilla.org/MPL/
+;+#
+;+# Software distributed under the License is distributed on an "AS
+;+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+;+# implied. See the License for the specific language governing
+;+# rights and limitations under the License.
+;+#
+;+# The Original Code is the Netscape security libraries.
+;+#
+;+# The Initial Developer of the Original Code is Netscape
+;+# Communications Corporation.  Portions created by Netscape are
+;+# Copyright (C) 2000 Netscape Communications Corporation.  All
+;+# Rights Reserved.
+;+#
+;+# Contributor(s): 
+;+#	Dr Stephen Henson <stephen.henson@gemplus.com>
+;+#
+;+# Alternatively, the contents of this file may be used under the
+;+# terms of the GNU General Public License Version 2 or later (the
+;+# "GPL"), in which case the provisions of the GPL are applicable
+;+# instead of those above.  If you wish to allow use of your
+;+# version of this file only under the terms of the GPL and not to
+;+# allow others to use your version of this file under the MPL,
+;+# indicate your decision by deleting the provisions above and
+;+# replace them with the notice and other provisions required by
+;+# the GPL.  If you do not delete the provisions above, a recipient
+;+# may use your version of this file under either the MPL or the
+;+# GPL.
+;+#
+;+#
+;+# OK, this file is meant to support SUN, LINUX, AIX and WINDOWS
+;+#   1. For all unix platforms, the string ";-"  means "remove this line"
+;+#   2. For all unix platforms, the string " DATA " will be removed from any 
+;+#	line on which it occurs.
+;+#   3. Lines containing ";+" will have ";+" removed on SUN and LINUX.
+;+#      On AIX, lines containing ";+" will be removed.  
+;+#   4. For all unix platforms, the string ";;" will thave the ";;" removed.
+;+#   5. For all unix platforms, after the above processing has taken place,
+;+#    all characters after the first ";" on the line will be removed.  
+;+#    And for AIX, the first ";" will also be removed.
+;+#  This file is passed directly to windows. Since ';' is a comment, all UNIX
+;+#   directives are hidden behind ";", ";+", and ";-"
+;+NSS_3.2 {       # NSS 3.2 release
+;+    global:
+LIBRARY nss3	;-
+EXPORTS		;-
+ATOB_AsciiToData;
+BTOA_ConvertItemToAscii;
+BTOA_DataToAscii;
+CERT_AsciiToName;
+CERT_CertTimesValid;
+CERT_CheckCertValidTimes;
+CERT_CreateCertificateRequest;
+CERT_ChangeCertTrust;
+CERT_DecodeDERCrl;
+CERT_DestroyCertificateRequest;
+CERT_DestroyCertList;
+CERT_DestroyName;
+CERT_EnableOCSPChecking;
+CERT_FormatName;
+CERT_DestroyCertificate;
+CERT_DupCertificate;
+CERT_FreeDistNames;
+CERT_FreeNicknames;
+CERT_GetAVATag;
+CERT_GetCertEmailAddress;
+CERT_GetCertNicknames;
+CERT_GetCertIssuerAndSN;
+CERT_GetCertTrust;
+CERT_GetCertUid;
+CERT_GetCommonName;
+CERT_GetCountryName;
+CERT_GetDBContentVersion;
+CERT_GetDefaultCertDB;
+CERT_GetDomainComponentName;
+CERT_GetLocalityName;
+CERT_GetOrgName;
+CERT_GetOrgUnitName;
+CERT_GetSSLCACerts;
+CERT_GetSlopTime;
+CERT_GetStateName;
+CERT_ImportCAChain;
+CERT_NameToAscii;
+CERT_RFC1485_EscapeAndQuote;
+CERT_SetSlopTime;
+CERT_VerifyCertName;
+CERT_VerifyCertNow;
+DER_UTCDayToAscii;
+DER_UTCTimeToAscii;
+DER_GeneralizedTimeToTime;
+NSS_Init;
+NSS_Initialize;
+NSS_InitReadWrite;
+NSS_NoDB_Init;
+NSS_Shutdown;
+NSS_VersionCheck;
+PK11_Authenticate;
+PK11_ChangePW;
+PK11_CheckUserPassword;
+PK11_CipherOp;
+PK11_CloneContext;
+PK11_ConfigurePKCS11;
+PK11_CreateContextBySymKey;
+PK11_CreateDigestContext;
+PK11_DestroyContext;
+PK11_DestroyTokenObject;
+PK11_DigestBegin;
+PK11_DigestOp;
+PK11_DigestFinal;
+PK11_DoesMechanism;
+PK11_FindCertFromNickname;
+PK11_FindCertFromDERCert;
+PK11_FindCertByIssuerAndSN;
+PK11_FindKeyByAnyCert;
+PK11_FindKeyByDERCert;
+PK11_FindSlotByName;
+PK11_Finalize;
+PK11_FortezzaHasKEA;
+PK11_FreeSlot;
+PK11_FreeSlotList;
+PK11_FreeSymKey;
+PK11_GenerateKeyPair;
+PK11_GenerateRandom;
+PK11_GenerateNewParam;
+PK11_GetAllTokens;
+PK11_GetBlockSize;
+PK11_GetFirstSafe;
+PK11_GetInternalKeySlot;
+PK11_GetInternalSlot;
+PK11_GetSlotName;
+PK11_GetTokenName;
+PK11_HashBuf;
+PK11_IsFIPS;
+PK11_IsFriendly;
+PK11_IsInternal;
+PK11_IsHW;
+PK11_IsPresent;
+PK11_IsReadOnly;
+PK11_KeyGen;
+PK11_ListCerts;
+PK11_NeedLogin;
+PK11_RandomUpdate;
+PK11_SetPasswordFunc;
+PK11_SetSlotPWValues;
+PORT_Alloc;
+PORT_Free;
+PORT_GetError;
+PORT_SetError;
+PORT_SetUCS4_UTF8ConversionFunction;
+PORT_SetUCS2_UTF8ConversionFunction;
+PORT_SetUCS2_ASCIIConversionFunction;
+SECITEM_CopyItem;
+SECITEM_DupItem;
+SECITEM_FreeItem;
+SECITEM_ZfreeItem;
+SECKEY_ConvertToPublicKey;
+SECKEY_CopyPrivateKey;
+SECKEY_CreateSubjectPublicKeyInfo;
+SECKEY_DestroyPrivateKey;
+SECKEY_DestroySubjectPublicKeyInfo;
+SECMOD_IsModulePresent;
+SECOID_FindOIDTagDescription;
+SECOID_GetAlgorithmTag;
+SEC_DeletePermCertificate;
+SEC_DeletePermCRL;
+SEC_DerSignData;
+SEC_DestroyCrl;
+SEC_FindCrlByDERCert;
+SEC_FindCrlByName;
+SEC_LookupCrls;
+SEC_NewCrl;
+;+#
+;+# The following symbols are exported only to make libssl3.so work. 
+;+# These are still private!!!
+;+#
+__CERT_NewTempCertificate;
+__PK11_CreateContextByRawKey;
+__PK11_GetKeyData;
+__nss_InitLock;
+CERT_CertChainFromCert;
+CERT_DestroyCertificateList;
+CERT_DupCertList;
+CERT_ExtractPublicKey;
+CERT_FindCertByName;
+DER_Lengths;
+DSAU_DecodeDerSig;
+DSAU_EncodeDerSig;
+HASH_GetHashObject;
+NSSRWLock_Destroy;
+NSSRWLock_HaveWriteLock;
+NSSRWLock_LockRead;
+NSSRWLock_LockWrite;
+NSSRWLock_New;
+NSSRWLock_UnlockRead;
+NSSRWLock_UnlockWrite;
+NSS_PutEnv;
+PK11_Derive;
+PK11_DeriveWithFlags;
+PK11_DigestKey;
+PK11_FindBestKEAMatch;
+PK11_FindFixedKey;
+PK11_GenerateFortezzaIV;
+PK11_GetBestKeyLength;
+PK11_GetBestSlot;
+PK11_GetBestSlotMultiple;
+PK11_GetBestWrapMechanism;
+PK11_GetCurrentWrapIndex;
+PK11_GetMechanism;
+PK11_GetModuleID;
+PK11_GetPrivateModulusLen;
+PK11_GetSlotFromKey;
+PK11_GetSlotFromPrivateKey;
+PK11_GetSlotID;
+PK11_GetSlotSeries;
+PK11_GetTokenInfo;
+PK11_GetWindow;
+PK11_GetWrapKey;
+PK11_IVFromParam;
+PK11_MakeKEAPubKey;
+PK11_ParamFromIV;
+PK11_PubDecryptRaw;
+PK11_PubDerive;
+PK11_PubEncryptRaw;
+PK11_PubUnwrapSymKey;
+PK11_PubWrapSymKey;
+PK11_ReferenceSymKey;
+PK11_RestoreContext;
+PK11_SaveContext;
+PK11_SetFortezzaHack;
+PK11_SetWrapKey;
+PK11_Sign;
+PK11_SignatureLen;
+PK11_SymKeyFromHandle;
+PK11_TokenExists;
+PK11_UnwrapSymKey;
+PK11_UnwrapSymKeyWithFlags;
+PK11_Verify;
+PK11_VerifyKeyOK;
+PK11_WrapSymKey;
+PORT_ArenaAlloc;
+PORT_ArenaZAlloc;
+PORT_FreeArena;
+PORT_NewArena;
+PORT_Realloc;
+PORT_ZAlloc;
+PORT_ZFree;
+RSA_FormatBlock;
+SECITEM_CompareItem;
+SECKEY_CreateRSAPrivateKey;
+SECKEY_DestroyPublicKey;
+SECKEY_PublicKeyStrength;
+SECKEY_UpdateCertPQG;
+SECMOD_LookupSlot;
+SGN_Begin;
+SGN_DestroyContext;
+SGN_End;
+SGN_NewContext;
+SGN_Update;
+VFY_Begin;
+VFY_CreateContext;
+VFY_DestroyContext;
+VFY_End;
+VFY_Update;
+;+#
+;+# The following symbols are exported only to make libsmime3.so work. 
+;+# These are still private!!!
+;+#
+__CERT_ClosePermCertDB;
+__CERT_DecodeDERCertificate;
+__CERT_TraversePermCertsForNickname;
+__CERT_TraversePermCertsForSubject;
+__PBE_CreateContext;
+__PBE_DestroyContext;
+__PBE_GenerateBits;
+ATOB_ConvertAsciiToItem;
+CERT_AddCertToListTail;
+CERT_CertListFromCert;
+CERT_DestroyCertArray;
+CERT_FindCertByDERCert;
+CERT_FindCertByIssuerAndSN;
+CERT_FindSMimeProfile;
+CERT_ImportCerts;
+CERT_NewCertList;
+CERT_OpenCertDBFilename;
+CERT_SaveSMimeProfile;
+CERT_VerifyCert;
+DER_GetInteger;
+DER_TimeToUTCTime;
+DER_UTCTimeToTime;
+PK11_AlgtagToMechanism;
+PK11_BlockData;
+PK11_CreatePBEAlgorithmID;
+PK11_DestroyObject;
+PK11_ExportEncryptedPrivateKeyInfo;
+PK11_ExportPrivateKeyInfo;
+PK11_FindCertAndKeyByRecipientList;
+PK11_FindCertAndKeyByRecipientListNew;
+PK11_FindCertInSlot;
+PK11_FindPrivateKeyFromCert;
+PK11_FortezzaMapSig;
+PK11_GetKeyLength;
+PK11_GetKeyStrength;
+PK11_ImportCertForKeyToSlot;
+PK11_ImportEncryptedPrivateKeyInfo;
+PK11_ImportPrivateKeyInfo;
+PK11_MapPBEMechanismToCryptoMechanism;
+PK11_PBEKeyGen;
+PK11_ParamFromAlgid;
+PK11_ParamToAlgid;
+PK11_TraverseCertsForNicknameInSlot;
+PK11_TraverseCertsForSubjectInSlot;
+PORT_ArenaGrow;
+PORT_ArenaMark;
+PORT_ArenaRelease;
+PORT_ArenaStrdup;
+PORT_ArenaUnmark;
+PORT_UCS2_ASCIIConversion;
+PORT_UCS2_UTF8Conversion;
+SECITEM_AllocItem;
+SECKEY_CopyEncryptedPrivateKeyInfo;
+SECKEY_CopyPrivateKeyInfo;
+SECKEY_DestroyEncryptedPrivateKeyInfo;
+SECKEY_DestroyPrivateKeyInfo;
+SECOID_CompareAlgorithmID;
+SECOID_CopyAlgorithmID;
+SECOID_DestroyAlgorithmID;
+SECOID_FindOID;
+SECOID_FindOIDByTag;
+SECOID_FindOIDTag;
+SECOID_SetAlgorithmID;
+SEC_ASN1DecodeInteger;
+SEC_ASN1DecodeItem;
+SEC_ASN1DecoderClearFilterProc;
+SEC_ASN1DecoderClearNotifyProc;
+SEC_ASN1DecoderFinish;
+SEC_ASN1DecoderSetFilterProc;
+SEC_ASN1DecoderSetNotifyProc;
+SEC_ASN1DecoderStart;
+SEC_ASN1DecoderUpdate;
+SEC_ASN1Encode;
+SEC_ASN1EncodeInteger;
+SEC_ASN1EncodeItem;
+SEC_ASN1EncoderClearNotifyProc;
+SEC_ASN1EncoderClearStreaming;
+SEC_ASN1EncoderClearTakeFromBuf;
+SEC_ASN1EncoderFinish;
+SEC_ASN1EncoderSetNotifyProc;
+SEC_ASN1EncoderSetStreaming;
+SEC_ASN1EncoderSetTakeFromBuf;
+SEC_ASN1EncoderStart;
+SEC_ASN1EncoderUpdate;
+SEC_ASN1LengthLength;
+SEC_PKCS5GetCryptoAlgorithm;
+SEC_PKCS5GetKeyLength;
+SEC_PKCS5GetPBEAlgorithm;
+SEC_PKCS5IsAlgorithmPBEAlg;
+SEC_SignData;
+SGN_CompareDigestInfo;
+SGN_CopyDigestInfo;
+SGN_CreateDigestInfo;
+SGN_DestroyDigestInfo;
+SGN_Digest;
+VFY_VerifyData;
+VFY_VerifyDigest;
+;+#
+;+# Data objects
+;+#
+;+# Don't export these DATA symbols on Windows because they don't work right.
+;;CERT_CrlTemplate DATA ;
+;;CERT_SignedDataTemplate DATA ;
+;;CERT_CertificateTemplate DATA ;
+;;CERT_CertificateRequestTemplate DATA ;
+;;CERT_IssuerAndSNTemplate DATA ;
+;;CERT_SetOfSignedCrlTemplate DATA ;
+;;SECKEY_DSAPublicKeyTemplate DATA ;
+;;SECKEY_EncryptedPrivateKeyInfoTemplate DATA ;
+;;SECKEY_PointerToEncryptedPrivateKeyInfoTemplate DATA ;
+;;SECKEY_PointerToPrivateKeyInfoTemplate DATA ;
+;;SECKEY_PrivateKeyInfoTemplate DATA ;
+;;SECKEY_RSAPublicKeyTemplate DATA ;
+;;SECOID_AlgorithmIDTemplate DATA ;
+;;SEC_AnyTemplate DATA ;
+;;SEC_BMPStringTemplate DATA ;
+;;SEC_BitStringTemplate DATA ;
+;;SEC_GeneralizedTimeTemplate DATA ;
+;;SEC_IA5StringTemplate DATA ;
+;;SEC_IntegerTemplate DATA ;
+;;SEC_ObjectIDTemplate DATA ;
+;;SEC_OctetStringTemplate DATA ;
+;;SEC_PointerToAnyTemplate DATA ;
+;;SEC_PointerToOctetStringTemplate DATA ;
+;;SEC_SetOfAnyTemplate DATA ;
+;;SEC_UTCTimeTemplate DATA ;
+;;sgn_DigestInfoTemplate DATA ;
+NSS_Get_CERT_CrlTemplate;
+NSS_Get_CERT_SignedDataTemplate;
+NSS_Get_CERT_CertificateTemplate;
+NSS_Get_CERT_CertificateRequestTemplate;
+NSS_Get_CERT_IssuerAndSNTemplate;
+NSS_Get_CERT_SetOfSignedCrlTemplate;
+NSS_Get_SECKEY_DSAPublicKeyTemplate;
+NSS_Get_SECKEY_EncryptedPrivateKeyInfoTemplate;
+NSS_Get_SECKEY_PointerToEncryptedPrivateKeyInfoTemplate;
+NSS_Get_SECKEY_PointerToPrivateKeyInfoTemplate;
+NSS_Get_SECKEY_PrivateKeyInfoTemplate;
+NSS_Get_SECKEY_RSAPublicKeyTemplate;
+NSS_Get_SECOID_AlgorithmIDTemplate;
+NSS_Get_SEC_AnyTemplate;
+NSS_Get_SEC_BMPStringTemplate;
+NSS_Get_SEC_BitStringTemplate;
+NSS_Get_SEC_GeneralizedTimeTemplate;
+NSS_Get_SEC_IA5StringTemplate;
+NSS_Get_SEC_IntegerTemplate;
+NSS_Get_SEC_ObjectIDTemplate;
+NSS_Get_SEC_OctetStringTemplate;
+NSS_Get_SEC_PointerToAnyTemplate;
+NSS_Get_SEC_PointerToOctetStringTemplate;
+NSS_Get_SEC_SetOfAnyTemplate;
+NSS_Get_SEC_UTCTimeTemplate;
+NSS_Get_sgn_DigestInfoTemplate;
+;+# commands
+CERT_DecodeBasicConstraintValue;
+CERT_DecodeOidSequence;
+CERT_DecodeUserNotice;
+CERT_DecodeCertificatePoliciesExtension;
+CERT_DestroyCertificatePoliciesExtension;
+CERT_FindCertByNicknameOrEmailAddr;
+CERT_FindCertByNickname;
+CERT_GenTime2FormattedAscii;
+CERT_Hexify;
+CERT_CompareName;
+PK11SDR_Encrypt;
+PK11SDR_Decrypt;
+NSSBase64Decoder_Create;
+NSSBase64Decoder_Destroy;
+NSSBase64Decoder_Update;
+NSSBase64Encoder_Create;
+NSSBase64Encoder_Destroy;
+NSSBase64Encoder_Update;
+;+#PK11_DoPassword;
+;+#PK11_FindKeyByKeyID;
+PK11_InitPin;
+PK11_NeedUserInit;
+;+    local:
+;+       *;
+;+};
+;+NSS_3.2.1 {       # NSS 3.2.1 release
+;+    global:
+CERT_AddRDN;
+CERT_CreateRDN;
+CERT_CreateAVA;
+CERT_CreateName;
+;+    local:
+;+       *;
+;+};
+;+NSS_3.3 { 	# NSS 3.3. release
+;+    global:
+CERT_CheckCertUsage;
+SECKEY_CreateDHPrivateKey;
+SECMOD_AddNewModule;
+;+#
+;+# The following symbols are exported only to make JSS work.
+;+# These are still private!!!
+;+#
+CERT_DisableOCSPChecking;
+CERT_DisableOCSPDefaultResponder;
+CERT_EnableOCSPDefaultResponder;
+CERT_GetCertTimes;
+CERT_ImportCAChainTrusted;
+CERT_ImportCRL;
+CERT_IsCACert;
+CERT_IsCADERCert;
+CERT_SetOCSPDefaultResponder;
+PBE_CreateContext;
+PBE_DestroyContext;
+PBE_GenerateBits;
+PK11_CheckSSOPassword;
+PK11_CopySymKeyForSigning;
+PK11_DeleteTokenCertAndKey;
+PK11_DEREncodePublicKey;
+PK11_ExtractKeyValue;
+PK11_FindCertsFromNickname;
+PK11_FindKeyByKeyID;
+PK11_GetIVLength;
+PK11_GetKeyData;
+PK11_GetKeyType;
+PK11_GetLowLevelKeyIDForCert;
+PK11_GetLowLevelKeyIDForPrivateKey;
+PK11_GetSlotPWValues;
+PK11_ImportCertForKey;
+PK11_ImportDERCertForKey;
+PK11_ImportDERPrivateKeyInfo;
+PK11_ImportSymKey;
+PK11_IsLoggedIn;
+PK11_KeyForDERCertExists;
+PK11_KeyForCertExists;
+PK11_ListPrivateKeysInSlot;
+PK11_ListCertsInSlot;
+PK11_Logout;
+PK11_NeedPWInit;
+PK11_MakeIDFromPubKey;
+PK11_PQG_DestroyParams;
+PK11_PQG_DestroyVerify;
+PK11_PQG_GetBaseFromParams;
+PK11_PQG_GetCounterFromVerify;
+PK11_PQG_GetHFromVerify;
+PK11_PQG_GetPrimeFromParams;
+PK11_PQG_GetSeedFromVerify;
+PK11_PQG_GetSubPrimeFromParams;
+PK11_PQG_NewParams;
+PK11_PQG_NewVerify;
+PK11_PQG_ParamGen;
+PK11_PQG_ParamGenSeedLen;
+PK11_PQG_VerifyParams;
+PK11_ReferenceSlot;
+PK11_SeedRandom;
+PK11_UnwrapPrivKey;
+PK11_VerifyRecover;
+PK11_WrapPrivKey;
+SEC_CertNicknameConflict;
+SEC_PKCS5GetIV;
+SECMOD_DeleteInternalModule;
+SECMOD_DestroyModule;
+SECMOD_GetDefaultModuleList;
+SECMOD_GetDefaultModuleListLock;
+SECMOD_GetInternalModule;
+SECMOD_GetReadLock;
+SECMOD_ReferenceModule;
+SECMOD_ReleaseReadLock;
+SECKEY_AddPrivateKeyToListTail;
+SECKEY_EncodeDERSubjectPublicKeyInfo;
+SECKEY_ExtractPublicKey;
+SECKEY_DestroyPrivateKeyList;
+SECKEY_GetPrivateKeyType;
+SECKEY_HashPassword;
+SECKEY_ImportDERPublicKey;
+SECKEY_NewPrivateKeyList;
+SECKEY_RemovePrivateKeyListNode;
+VFY_EndWithSignature;
+;+    local:
+;+       *;
+;+};
new file mode 100644
--- /dev/null
+++ b/security/nss/lib/pk11wrap/pk11kea.c
@@ -0,0 +1,224 @@
+/*
+ * The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ * 
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ * 
+ * The Original Code is the Netscape security libraries.
+ * 
+ * The Initial Developer of the Original Code is Netscape
+ * Communications Corporation.  Portions created by Netscape are 
+ * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
+ * Rights Reserved.
+ * 
+ * Contributor(s):
+ * 
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU General Public License Version 2 or later (the
+ * "GPL"), in which case the provisions of the GPL are applicable 
+ * instead of those above.  If you wish to allow use of your 
+ * version of this file only under the terms of the GPL and not to
+ * allow others to use your version of this file under the MPL,
+ * indicate your decision by deleting the provisions above and
+ * replace them with the notice and other provisions required by
+ * the GPL.  If you do not delete the provisions above, a recipient
+ * may use your version of this file under either the MPL or the
+ * GPL.
+ */
+/*
+ * This file implements the Symkey wrapper and the PKCS context
+ * Interfaces.
+ */
+
+#include "seccomon.h"
+#include "secmod.h"
+#include "nssilock.h"
+#include "secmodi.h"
+#include "pkcs11.h"
+#include "pk11func.h"
+#include "secitem.h"
+#include "key.h"
+#include "secasn1.h"
+#include "sechash.h"
+#include "cert.h"
+#include "secerr.h"
+
+/*
+ * find an RSA public key on a card
+ */
+static CK_OBJECT_HANDLE
+pk11_FindRSAPubKey(PK11SlotInfo *slot)
+{
+    CK_KEY_TYPE key_type = CKK_RSA;
+    CK_OBJECT_CLASS class_type = CKO_PUBLIC_KEY;
+    CK_ATTRIBUTE theTemplate[2];
+    int template_count = sizeof(theTemplate)/sizeof(theTemplate[0]);
+    CK_ATTRIBUTE *attrs = theTemplate;
+
+    PK11_SETATTRS(attrs,CKA_CLASS,&class_type,sizeof(class_type)); attrs++;
+    PK11_SETATTRS(attrs,CKA_KEY_TYPE,&key_type,sizeof(key_type)); attrs++;
+    template_count = attrs - theTemplate;
+    PR_ASSERT(template_count <= sizeof(theTemplate)/sizeof(CK_ATTRIBUTE));
+
+    return pk11_FindObjectByTemplate(slot,theTemplate,template_count);
+}
+
+SECKEYPublicKey *PK11_ExtractPublicKey(PK11SlotInfo *slot, KeyType keyType,
+					 CK_OBJECT_HANDLE id);
+
+PK11SymKey *
+pk11_KeyExchange(PK11SlotInfo *slot,CK_MECHANISM_TYPE type,
+		 	CK_ATTRIBUTE_TYPE operation, PK11SymKey *symKey)
+{
+    PK11SymKey *newSymKey = NULL;
+    SECStatus rv;
+    /* performance improvement can go here --- use a generated key to as a
+     * per startup wrapping key. If it exists, use it, otherwise do a full
+     * key exchange. */
+
+    /* find a common Key Exchange algorithm */
+    /* RSA */
+    if (PK11_DoesMechanism(symKey->slot, CKM_RSA_PKCS) && 
+				PK11_DoesMechanism(slot,CKM_RSA_PKCS)) {
+	CK_OBJECT_HANDLE pubKeyHandle = CK_INVALID_KEY;
+	CK_OBJECT_HANDLE privKeyHandle = CK_INVALID_KEY;
+	SECKEYPublicKey *pubKey = NULL;
+	SECKEYPrivateKey *privKey = NULL;
+	SECItem wrapData;
+
+	wrapData.data = NULL;
+
+	/* find RSA Public Key on target */
+	pubKeyHandle = pk11_FindRSAPubKey(slot);
+	if (pubKeyHandle != CK_INVALID_KEY) {
+	    privKeyHandle = PK11_MatchItem(slot,pubKeyHandle,CKO_PRIVATE_KEY);
+	}
+
+	/* if no key exits, generate a key pair */
+	if (privKeyHandle == CK_INVALID_KEY) {
+	    unsigned int     keyLength = PK11_GetKeyLength(symKey);
+	    PK11RSAGenParams rsaParams;
+
+	    rsaParams.keySizeInBits = 
+		((keyLength == 0) || (keyLength > 16)) ? 512 : 256;
+	    rsaParams.pe  = 0x10001;
+	    privKey = PK11_GenerateKeyPair(slot,CKM_RSA_PKCS_KEY_PAIR_GEN, 
+		&rsaParams, &pubKey,PR_FALSE,PR_TRUE,symKey->cx);
+	} else {
+	    /* if key's exist, build SECKEY data structures for them */
+	    privKey = PK11_MakePrivKey(slot,nullKey, PR_TRUE, privKeyHandle,
+					symKey->cx);
+	    if (privKey != NULL) {
+    		pubKey = PK11_ExtractPublicKey(slot, rsaKey, pubKeyHandle);
+		if (pubKey && pubKey->pkcs11Slot) {
+		    PK11_FreeSlot(pubKey->pkcs11Slot);
+		    pubKey->pkcs11Slot = NULL;
+		    pubKey->pkcs11ID = CK_INVALID_KEY;
+		}
+	    }
+	}
+	if (privKey == NULL) goto rsa_failed;
+	if (pubKey == NULL)  goto rsa_failed;
+
+        wrapData.len  = SECKEY_PublicKeyStrength(pubKey);
+        if (!wrapData.len) goto rsa_failed;
+        wrapData.data = PORT_Alloc(wrapData.len);
+        if (wrapData.data == NULL) goto rsa_failed;
+
+	/* now wrap the keys in and out */
+	rv = PK11_PubWrapSymKey(CKM_RSA_PKCS, pubKey, symKey, &wrapData);
+	if (rv == SECSuccess) {
+	    newSymKey = PK11_PubUnwrapSymKey(privKey,&wrapData,type,operation,
+							symKey->size);
+	}
+rsa_failed:
+	if (wrapData.data != NULL) PORT_Free(wrapData.data);
+	if (privKey != NULL) SECKEY_DestroyPrivateKey(privKey);
+	if (pubKey != NULL) SECKEY_DestroyPublicKey(pubKey);
+
+	return  newSymKey;
+    }
+    /* KEA */
+    if (PK11_DoesMechanism(symKey->slot, CKM_KEA_KEY_DERIVE) && 
+				PK11_DoesMechanism(slot,CKM_KEA_KEY_DERIVE)) {
+	CERTCertificate *certSource = NULL;
+	CERTCertificate *certTarget = NULL;
+	SECKEYPublicKey *pubKeySource = NULL;
+	SECKEYPublicKey *pubKeyTarget = NULL;
+	SECKEYPrivateKey *privKeySource = NULL;
+	SECKEYPrivateKey *privKeyTarget = NULL;
+	PK11SymKey *tekSource = NULL;
+	PK11SymKey *tekTarget = NULL;
+	SECItem Ra,wrap;
+
+	/* can only exchange skipjack keys */
+	if (type != CKM_SKIPJACK_CBC64) {
+    	    PORT_SetError( SEC_ERROR_NO_MODULE );
+	    goto kea_failed;
+	}
+
+	/* find a pair of certs we can use */
+	rv = PK11_GetKEAMatchedCerts(symKey->slot,slot,&certSource,&certTarget);
+	if (rv != SECSuccess) goto kea_failed;
+
+	/* get all the key pairs */
+	pubKeyTarget = CERT_ExtractPublicKey(certSource);
+	pubKeySource = CERT_ExtractPublicKey(certTarget);
+	privKeySource = 
+		PK11_FindKeyByDERCert(symKey->slot,certSource,symKey->cx);
+	privKeyTarget = 
+		PK11_FindKeyByDERCert(slot,certTarget,symKey->cx);
+
+	if ((pubKeySource == NULL) || (pubKeyTarget == NULL) ||
+	  (privKeySource == NULL) || (privKeyTarget == NULL)) goto kea_failed;
+
+	/* generate the wrapping TEK's */
+	Ra.data = (unsigned char*)PORT_Alloc(128 /* FORTEZZA RA MAGIC */);
+	Ra.len = 128;
+	if (Ra.data == NULL) goto kea_failed;
+
+	tekSource = PK11_PubDerive(privKeySource,pubKeyTarget,PR_TRUE,&Ra,NULL,
+		CKM_SKIPJACK_WRAP, CKM_KEA_KEY_DERIVE,CKA_WRAP,0,symKey->cx);
+	tekTarget = PK11_PubDerive(privKeyTarget,pubKeySource,PR_FALSE,&Ra,NULL,
+		CKM_SKIPJACK_WRAP, CKM_KEA_KEY_DERIVE,CKA_WRAP,0,symKey->cx);
+	PORT_Free(Ra.data);
+
+	if ((tekSource == NULL) || (tekTarget == NULL)) { goto kea_failed; }
+
+	/* wrap the key out of Source into target */
+	wrap.data = (unsigned char*)PORT_Alloc(12); /* MAGIC SKIPJACK LEN */
+	wrap.len = 12;
+
+	/* paranoia to prevent infinite recursion on bugs */
+	PORT_Assert(tekSource->slot == symKey->slot);
+	if (tekSource->slot != symKey->slot) {
+    	    PORT_SetError( SEC_ERROR_NO_MODULE );
+	    goto kea_failed;
+	}
+
+	rv = PK11_WrapSymKey(CKM_SKIPJACK_WRAP,NULL,tekSource,symKey,&wrap);
+	if (rv == SECSuccess) {
+	    newSymKey = PK11_UnwrapSymKey(tekTarget, CKM_SKIPJACK_WRAP, NULL,
+			&wrap, type, operation, symKey->size);
+	}
+	PORT_Free(wrap.data);
+kea_failed:
+	if (certSource == NULL) CERT_DestroyCertificate(certSource);
+	if (certTarget == NULL) CERT_DestroyCertificate(certTarget);
+	if (pubKeySource == NULL) SECKEY_DestroyPublicKey(pubKeySource);
+	if (pubKeyTarget == NULL) SECKEY_DestroyPublicKey(pubKeyTarget);
+	if (privKeySource == NULL) SECKEY_DestroyPrivateKey(privKeySource);
+	if (privKeyTarget == NULL) SECKEY_DestroyPrivateKey(privKeyTarget);
+	if (tekSource == NULL) PK11_FreeSymKey(tekSource);
+	if (tekTarget == NULL) PK11_FreeSymKey(tekTarget);
+	return newSymKey;
+    }
+    PORT_SetError( SEC_ERROR_NO_MODULE );
+    return NULL;
+}
+
new file mode 100644
--- /dev/null
+++ b/security/nss/lib/pkcs12/p12d.c
@@ -0,0 +1,3230 @@
+/*
+ * The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ * 
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ * 
+ * The Original Code is the Netscape security libraries.
+ * 
+ * The Initial Developer of the Original Code is Netscape
+ * Communications Corporation.  Portions created by Netscape are 
+ * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
+ * Rights Reserved.
+ * 
+ * Contributor(s):
+ * 
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU General Public License Version 2 or later (the
+ * "GPL"), in which case the provisions of the GPL are applicable 
+ * instead of those above.  If you wish to allow use of your 
+ * version of this file only under the terms of the GPL and not to
+ * allow others to use your version of this file under the MPL,
+ * indicate your decision by deleting the provisions above and
+ * replace them with the notice and other provisions required by
+ * the GPL.  If you do not delete the provisions above, a recipient
+ * may use your version of this file under either the MPL or the
+ * GPL.
+ */
+
+
+#include "nssrenam.h"
+#include "p12t.h"
+#include "p12.h"
+#include "plarena.h"
+#include "secitem.h"
+#include "secoid.h"
+#include "seccomon.h"
+#include "secport.h"
+#include "cert.h"
+#include "secpkcs7.h"
+#include "secasn1.h"
+#include "secerr.h"
+#include "pk11func.h"
+#include "p12plcy.h"
+#include "p12local.h"
+#include "alghmac.h"
+#include "secder.h"
+#include "secport.h"
+
+#include "certdb.h"
+
+#include "prcpucfg.h"
+
+typedef struct sec_PKCS12SafeContentsContextStr sec_PKCS12SafeContentsContext;
+
+/* Opaque structure for decoding SafeContents.  These are used
+ * for each authenticated safe as well as any nested safe contents.
+ */
+struct sec_PKCS12SafeContentsContextStr {
+    /* the parent decoder context */
+    SEC_PKCS12DecoderContext *p12dcx;
+
+    /* memory arena to allocate space from */
+    PRArenaPool *arena;
+
+    /* decoder context and destination for decoding safe contents */
+    SEC_ASN1DecoderContext *safeContentsDcx;
+    sec_PKCS12SafeContents safeContents;
+
+    /* information for decoding safe bags within the safe contents.
+     * these variables are updated for each safe bag decoded.
+     */
+    SEC_ASN1DecoderContext *currentSafeBagDcx;
+    sec_PKCS12SafeBag *currentSafeBag;
+    PRBool skipCurrentSafeBag;
+
+    /* if the safe contents is nested, the parent is pointed to here. */
+    sec_PKCS12SafeContentsContext *nestedCtx;
+};
+
+/* opaque decoder context structure.  information for decoding a pkcs 12
+ * PDU are stored here as well as decoding pointers for intermediary 
+ * structures which are part of the PKCS 12 PDU.  Upon a successful
+ * decode, the safe bags containing certificates and keys encountered.
+ */  
+struct SEC_PKCS12DecoderContextStr {
+    PRArenaPool *arena;
+    PK11SlotInfo *slot;
+    void *wincx;
+    PRBool error;
+    int errorValue;
+
+    /* password */
+    SECItem *pwitem;
+
+    /* used for decoding the PFX structure */
+    SEC_ASN1DecoderContext *pfxDcx;
+    sec_PKCS12PFXItem pfx;
+
+    /* safe bags found during decoding */  
+    sec_PKCS12SafeBag **safeBags;
+    unsigned int safeBagCount;
+
+    /* state variables for decoding authenticated safes. */
+    SEC_PKCS7DecoderContext *currentASafeP7Dcx;
+    SEC_PKCS5KeyAndPassword *currentASafeKeyPwd;
+    SEC_ASN1DecoderContext *aSafeDcx;
+    SEC_PKCS7DecoderContext *aSafeP7Dcx;
+    sec_PKCS12AuthenticatedSafe authSafe;
+    SEC_PKCS7ContentInfo *aSafeCinfo;
+    sec_PKCS12SafeContents safeContents;
+
+    /* safe contents info */
+    unsigned int safeContentsCnt;
+    sec_PKCS12SafeContentsContext **safeContentsList;
+
+    /* HMAC info */
+    sec_PKCS12MacData	macData;
+    SEC_ASN1DecoderContext *hmacDcx;
+
+    /* routines for reading back the data to be hmac'd */
+    digestOpenFn dOpen;
+    digestCloseFn dClose;
+    digestIOFn dRead, dWrite;
+    void *dArg;
+
+    /* helper functions */
+    SECKEYGetPasswordKey pwfn;
+    void *pwfnarg;
+    PRBool swapUnicodeBytes;
+
+    /* import information */
+    PRBool bagsVerified;
+};
+
+
+/* make sure that the PFX version being decoded is a version
+ * which we support.
+ */
+static PRBool
+sec_pkcs12_proper_version(sec_PKCS12PFXItem *pfx)
+{
+    /* if no version, assume it is not supported */
+    if(pfx->version.len == 0) {
+	return PR_FALSE;
+    }
+
+    if(DER_GetInteger(&pfx->version) > SEC_PKCS12_VERSION) {
+	return PR_FALSE;
+    }
+
+    return PR_TRUE;
+}
+
+/* retrieve the key for decrypting the safe contents */ 
+static PK11SymKey *
+sec_pkcs12_decoder_get_decrypt_key(void *arg, SECAlgorithmID *algid)
+{
+    SEC_PKCS5KeyAndPassword *keyPwd = 
+        (SEC_PKCS5KeyAndPassword *)arg;
+
+    if(!keyPwd) {
+	return NULL;
+    }
+
+    /* if no slot specified, use the internal key slot */
+    if(!keyPwd->slot) {
+	keyPwd->slot = PK11_GetInternalKeySlot();
+    }
+
+    /* retrieve the key */
+    if(!keyPwd->key) {
+	keyPwd->key = PK11_PBEKeyGen(keyPwd->slot, algid, 
+				     keyPwd->pwitem, PR_FALSE, keyPwd->wincx);
+    }
+
+    return (PK11SymKey *)keyPwd;
+}
+
+/* XXX this needs to be modified to handle enveloped data.  most
+ * likely, it should mirror the routines for SMIME in that regard.
+ */
+static PRBool
+sec_pkcs12_decoder_decryption_allowed(SECAlgorithmID *algid, 
+				      PK11SymKey *bulkkey)
+{
+    PRBool decryptionAllowed = SEC_PKCS12DecryptionAllowed(algid);
+
+    if(!decryptionAllowed) {
+	return PR_FALSE;
+    }
+
+    return PR_TRUE;
+}
+
+/* when we encounter a new safe bag during the decoding, we need
+ * to allocate space for the bag to be decoded to and set the 
+ * state variables appropriately.  all of the safe bags are allocated
+ * in a buffer in the outer SEC_PKCS12DecoderContext, however,
+ * a pointer to the safeBag is also used in the sec_PKCS12SafeContentsContext
+ * for the current bag.
+ */
+static SECStatus
+sec_pkcs12_decoder_init_new_safe_bag(sec_PKCS12SafeContentsContext 
+						*safeContentsCtx)
+{
+    void *mark = NULL;
+    SEC_PKCS12DecoderContext *p12dcx;
+
+    /* make sure that the structures are defined, and there has
+     * not been an error in the decoding 
+     */
+    if(!safeContentsCtx || !safeContentsCtx->p12dcx 
+		|| safeContentsCtx->p12dcx->error) {
+	return SECFailure;
+    }
+
+    p12dcx = safeContentsCtx->p12dcx;
+    mark = PORT_ArenaMark(p12dcx->arena);
+
+    /* allocate a new safe bag, if bags already exist, grow the 
+     * list of bags, otherwise allocate a new list.  the list is
+     * NULL terminated.
+     */
+    if(p12dcx->safeBagCount) {
+	p12dcx->safeBags = 
+	    (sec_PKCS12SafeBag**)PORT_ArenaGrow(p12dcx->arena,p12dcx->safeBags,
+			(p12dcx->safeBagCount + 1) * sizeof(sec_PKCS12SafeBag *),
+			(p12dcx->safeBagCount + 2) * sizeof(sec_PKCS12SafeBag *));
+    } else {
+	p12dcx->safeBags = (sec_PKCS12SafeBag**)PORT_ArenaZAlloc(p12dcx->arena,
+					    2 * sizeof(sec_PKCS12SafeBag *));
+    }
+    if(!p12dcx->safeBags) {
+	p12dcx->errorValue = SEC_ERROR_NO_MEMORY;
+	goto loser;
+    }
+
+    /* append the bag to the end of the list and update the reference
+     * in the safeContentsCtx.
+     */
+    p12dcx->safeBags[p12dcx->safeBagCount] = 
+        (sec_PKCS12SafeBag*)PORT_ArenaZAlloc(p12dcx->arena,
+					     sizeof(sec_PKCS12SafeBag));
+    safeContentsCtx->currentSafeBag = p12dcx->safeBags[p12dcx->safeBagCount];
+    p12dcx->safeBags[++p12dcx->safeBagCount] = NULL;
+    if(!safeContentsCtx->currentSafeBag) {
+	p12dcx->errorValue = SEC_ERROR_NO_MEMORY;
+	goto loser;
+    }
+
+    safeContentsCtx->currentSafeBag->slot = safeContentsCtx->p12dcx->slot;
+    safeContentsCtx->currentSafeBag->pwitem = safeContentsCtx->p12dcx->pwitem;
+    safeContentsCtx->currentSafeBag->swapUnicodeBytes = 
+				safeContentsCtx->p12dcx->swapUnicodeBytes;
+    safeContentsCtx->currentSafeBag->arena = safeContentsCtx->p12dcx->arena;
+
+    PORT_ArenaUnmark(p12dcx->arena, mark);
+    return SECSuccess;
+
+loser:
+
+    /* if an error occurred, release the memory and set the error flag
+     * the only possible errors triggered by this function are memory 
+     * related.
+     */
+    if(mark) {
+	PORT_ArenaRelease(p12dcx->arena, mark);
+    }
+
+    p12dcx->error = PR_TRUE;
+    return SECFailure;
+}
+
+/* A wrapper for updating the ASN1 context in which a safeBag is
+ * being decoded.  This function is called as a callback from
+ * secasn1d when decoding SafeContents structures.
+ */
+static void
+sec_pkcs12_decoder_safe_bag_update(void *arg, const char *data, 
+				   unsigned long len, int depth, 
+				   SEC_ASN1EncodingPart data_kind)
+{
+    sec_PKCS12SafeContentsContext *safeContentsCtx = 
+        (sec_PKCS12SafeContentsContext *)arg;
+    SEC_PKCS12DecoderContext *p12dcx;
+    SECStatus rv;
+
+    /* make sure that we are not skipping the current safeBag,
+     * and that there are no errors.  If so, just return rather
+     * than continuing to process.
+     */
+    if(!safeContentsCtx || !safeContentsCtx->p12dcx 
+		|| safeContentsCtx->p12dcx->error 
+		|| safeContentsCtx->skipCurrentSafeBag) {
+	return;
+    }
+    p12dcx = safeContentsCtx->p12dcx;
+
+    rv = SEC_ASN1DecoderUpdate(safeContentsCtx->currentSafeBagDcx, data, len);
+    if(rv != SECSuccess) {
+	p12dcx->errorValue = SEC_ERROR_NO_MEMORY;
+	goto loser;
+    }
+
+    return;
+
+loser:
+    /* set the error, and finish the decoder context.  because there 
+     * is not a way of returning an error message, it may be worth
+     * while to do a check higher up and finish any decoding contexts
+     * that are still open.
+     */
+    p12dcx->error = PR_TRUE;
+    SEC_ASN1DecoderFinish(safeContentsCtx->currentSafeBagDcx);
+    safeContentsCtx->currentSafeBagDcx = NULL;
+    return;
+}
+
+/* forward declarations of functions that are used when decoding
+ * safeContents bags which are nested and when decoding the 
+ * authenticatedSafes.
+ */
+static SECStatus
+sec_pkcs12_decoder_begin_nested_safe_contents(sec_PKCS12SafeContentsContext 
+							*safeContentsCtx);
+static SECStatus
+sec_pkcs12_decoder_finish_nested_safe_contents(sec_PKCS12SafeContentsContext
+							*safeContentsCtx);
+static void
+sec_pkcs12_decoder_safe_bag_update(void *arg, const char *data, 
+				   unsigned long len, int depth, 
+				   SEC_ASN1EncodingPart data_kind);
+
+/* notify function for decoding safeBags.  This function is
+ * used to filter safeBag types which are not supported,
+ * initiate the decoding of nested safe contents, and decode
+ * safeBags in general.  this function is set when the decoder
+ * context for the safeBag is first created.
+ */
+static void
+sec_pkcs12_decoder_safe_bag_notify(void *arg, PRBool before,
+				   void *dest, int real_depth)
+{
+    sec_PKCS12SafeContentsContext *safeContentsCtx = 
+        (sec_PKCS12SafeContentsContext *)arg;
+    SEC_PKCS12DecoderContext *p12dcx;
+    sec_PKCS12SafeBag *bag;
+    PRBool after;
+
+    /* if an error is encountered, return */
+    if(!safeContentsCtx || !safeContentsCtx->p12dcx || 
+		safeContentsCtx->p12dcx->error) {
+	return;
+    }
+    p12dcx = safeContentsCtx->p12dcx;
+
+    /* to make things more readable */
+    if(before)
+	after = PR_FALSE;
+    else 
+	after = PR_TRUE;
+
+    /* have we determined the safeBagType yet? */
+    bag = safeContentsCtx->currentSafeBag;
+    if(bag->bagTypeTag == NULL) {
+	if(after && (dest == &(bag->safeBagType))) {
+	    bag->bagTypeTag = SECOID_FindOID(&(bag->safeBagType));
+	    if(bag->bagTypeTag == NULL) {
+		p12dcx->error = PR_TRUE;
+		p12dcx->errorValue = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE;
+	    }
+	}
+	return;
+    }
+
+    /* process the safeBag depending on it's type.  those
+     * which we do not support, are ignored.  we start a decoding
+     * context for a nested safeContents.
+     */
+    switch(bag->bagTypeTag->offset) {
+	case SEC_OID_PKCS12_V1_KEY_BAG_ID:
+	case SEC_OID_PKCS12_V1_CERT_BAG_ID:
+	case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID:
+	    break;
+	case SEC_OID_PKCS12_V1_SAFE_CONTENTS_BAG_ID:
+	    /* if we are just starting to decode the safeContents, initialize
+	     * a new safeContentsCtx to process it.
+	     */
+	    if(before && (dest == &(bag->safeBagContent))) {
+		sec_pkcs12_decoder_begin_nested_safe_contents(safeContentsCtx);
+	    } else if(after && (dest == &(bag->safeBagContent))) {
+		/* clean up the nested decoding */
+		sec_pkcs12_decoder_finish_nested_safe_contents(safeContentsCtx);
+	    }
+	    break;
+	case SEC_OID_PKCS12_V1_CRL_BAG_ID:
+	case SEC_OID_PKCS12_V1_SECRET_BAG_ID:
+	default:
+	    /* skip any safe bag types we don't understand or handle */
+	    safeContentsCtx->skipCurrentSafeBag = PR_TRUE;
+	    break;
+    }
+
+    return;
+}
+
+/* notify function for decoding safe contents.  each entry in the
+ * safe contents is a safeBag which needs to be allocated and
+ * the decoding context initialized at the beginning and then
+ * the context needs to be closed and finished at the end.
+ *
+ * this function is set when the safeContents decode context is
+ * initialized.
+ */
+static void
+sec_pkcs12_decoder_safe_contents_notify(void *arg, PRBool before,
+					void *dest, int real_depth)
+{
+    sec_PKCS12SafeContentsContext *safeContentsCtx = 
+        (sec_PKCS12SafeContentsContext*)arg;
+    SEC_PKCS12DecoderContext *p12dcx;
+    SECStatus rv;
+
+    /* if there is an error we don't want to continue processing,
+     * just return and keep going.
+     */
+    if(!safeContentsCtx || !safeContentsCtx->p12dcx 
+		|| safeContentsCtx->p12dcx->error) {
+	return;
+    }
+    p12dcx = safeContentsCtx->p12dcx;
+
+    /* if we are done with the current safeBag, then we need to
+     * finish the context and set the state variables appropriately.
+     */
+    if(!before) {
+	SEC_ASN1DecoderClearFilterProc(safeContentsCtx->safeContentsDcx);
+	SEC_ASN1DecoderFinish(safeContentsCtx->currentSafeBagDcx);
+	safeContentsCtx->currentSafeBagDcx = NULL;
+	safeContentsCtx->skipCurrentSafeBag = PR_FALSE;
+    } else {
+	/* we are starting a new safe bag.  we need to allocate space
+	 * for the bag and initialize the decoding context.
+	 */
+	rv = sec_pkcs12_decoder_init_new_safe_bag(safeContentsCtx);
+	if(rv != SECSuccess) {
+	    goto loser;
+	}
+
+	/* set up the decoder context */
+	safeContentsCtx->currentSafeBagDcx = SEC_ASN1DecoderStart(p12dcx->arena,
+						safeContentsCtx->currentSafeBag,
+						sec_PKCS12SafeBagTemplate);
+	if(!safeContentsCtx->currentSafeBagDcx) {
+	    p12dcx->errorValue = SEC_ERROR_NO_MEMORY;
+	    goto loser;
+	}
+
+	/* set the notify and filter procs so that the safe bag
+	 * data gets sent to the proper location when decoding.
+	 */
+	SEC_ASN1DecoderSetNotifyProc(safeContentsCtx->currentSafeBagDcx, 
+				 sec_pkcs12_decoder_safe_bag_notify, 
+				 safeContentsCtx);
+	SEC_ASN1DecoderSetFilterProc(safeContentsCtx->safeContentsDcx, 
+				 sec_pkcs12_decoder_safe_bag_update, 
+				 safeContentsCtx, PR_TRUE);
+    }
+
+    return;
+
+loser:
+    /* in the event of an error, we want to close the decoding
+     * context and clear the filter and notify procedures.
+     */
+    p12dcx->error = PR_TRUE;
+
+    if(safeContentsCtx->currentSafeBagDcx) {
+	SEC_ASN1DecoderFinish(safeContentsCtx->currentSafeBagDcx);
+	safeContentsCtx->currentSafeBagDcx = NULL;
+    }
+
+    SEC_ASN1DecoderClearNotifyProc(safeContentsCtx->safeContentsDcx);
+    SEC_ASN1DecoderClearFilterProc(safeContentsCtx->safeContentsDcx);
+
+    return;
+}
+
+/* initialize the safeContents for decoding.  this routine
+ * is used for authenticatedSafes as well as nested safeContents.
+ */
+static sec_PKCS12SafeContentsContext *
+sec_pkcs12_decoder_safe_contents_init_decode(SEC_PKCS12DecoderContext *p12dcx,
+					     PRBool nestedSafe)
+{
+    sec_PKCS12SafeContentsContext *safeContentsCtx = NULL;
+    const SEC_ASN1Template *theTemplate;
+
+    if(!p12dcx || p12dcx->error) {
+	return NULL;
+    }
+
+    /* allocate a new safeContents list or grow the existing list and
+     * append the new safeContents onto the end.
+     */
+    if(!p12dcx->safeContentsCnt) {
+	p12dcx->safeContentsList = 
+	    (sec_PKCS12SafeContentsContext**)PORT_ArenaZAlloc(p12dcx->arena, 
+	       			 sizeof(sec_PKCS12SafeContentsContext *));
+    } else {
+	p12dcx->safeContentsList = 
+	   (sec_PKCS12SafeContentsContext **) PORT_ArenaGrow(p12dcx->arena,
+			p12dcx->safeContentsList,
+			(p12dcx->safeContentsCnt * 
+				sizeof(sec_PKCS12SafeContentsContext *)),
+			(1 + p12dcx->safeContentsCnt * 
+				sizeof(sec_PKCS12SafeContentsContext *)));
+    }
+    if(!p12dcx->safeContentsList) {
+	p12dcx->errorValue = SEC_ERROR_NO_MEMORY;
+	goto loser;
+    }
+
+    p12dcx->safeContentsList[p12dcx->safeContentsCnt] = 
+        (sec_PKCS12SafeContentsContext*)PORT_ArenaZAlloc(
+					p12dcx->arena,
+					sizeof(sec_PKCS12SafeContentsContext));
+    p12dcx->safeContentsList[p12dcx->safeContentsCnt+1] = NULL;
+    if(!p12dcx->safeContentsList[p12dcx->safeContentsCnt]) {
+	p12dcx->errorValue = SEC_ERROR_NO_MEMORY;
+	goto loser;
+    }
+
+    /* set up the state variables */
+    safeContentsCtx = p12dcx->safeContentsList[p12dcx->safeContentsCnt];
+    p12dcx->safeContentsCnt++;
+    safeContentsCtx->p12dcx = p12dcx;
+    safeContentsCtx->arena = p12dcx->arena;
+
+    /* begin the decoding -- the template is based on whether we are
+     * decoding a nested safeContents or not.
+     */
+    if(nestedSafe == PR_TRUE) {
+	theTemplate = sec_PKCS12NestedSafeContentsDecodeTemplate;
+    } else {
+	theTemplate = sec_PKCS12SafeContentsDecodeTemplate;
+    }
+
+    /* start the decoder context */
+    safeContentsCtx->safeContentsDcx = SEC_ASN1DecoderStart(p12dcx->arena, 
+					&safeContentsCtx->safeContents,
+					theTemplate);
+	
+    if(!safeContentsCtx->safeContentsDcx) {
+	p12dcx->errorValue = SEC_ERROR_NO_MEMORY;
+	goto loser;
+    }
+
+    /* set the safeContents notify procedure to look for
+     * and start the decode of safeBags.
+     */
+    SEC_ASN1DecoderSetNotifyProc(safeContentsCtx->safeContentsDcx, 
+				sec_pkcs12_decoder_safe_contents_notify,
+				safeContentsCtx);
+
+    return safeContentsCtx;
+
+loser:
+    /* in the case of an error, we want to finish the decoder
+     * context and set the error flag.
+     */
+    if(safeContentsCtx && safeContentsCtx->safeContentsDcx) {
+	SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsDcx);
+	safeContentsCtx->safeContentsDcx = NULL;
+    }
+
+    p12dcx->error = PR_TRUE;
+
+    return NULL;
+}
+
+/* wrapper for updating safeContents.  this is set as the filter of
+ * safeBag when there is a nested safeContents.
+ */
+static void
+sec_pkcs12_decoder_nested_safe_contents_update(void *arg, const char *buf,
+					  unsigned long len, int depth,
+					  SEC_ASN1EncodingPart data_kind)
+{
+    sec_PKCS12SafeContentsContext *safeContentsCtx = 
+        (sec_PKCS12SafeContentsContext *)arg;
+    SEC_PKCS12DecoderContext *p12dcx;
+    SECStatus rv;
+
+    /* check for an error */
+    if(!safeContentsCtx || !safeContentsCtx->p12dcx 
+			|| safeContentsCtx->p12dcx->error) {
+	return;
+    }
+
+    /* no need to update if no data sent in */
+    if(!len || !buf) {
+	return;
+    }
+
+    /* update the decoding context */
+    p12dcx = safeContentsCtx->p12dcx;
+    rv = SEC_ASN1DecoderUpdate(safeContentsCtx->safeContentsDcx, buf, len);
+    if(rv != SECSuccess) {
+	p12dcx->errorValue = SEC_ERROR_NO_MEMORY;
+	goto loser;
+    }
+
+    return;
+
+loser:
+    /* handle any errors.  If a decoding context is open, close it. */
+    p12dcx->error = PR_TRUE;
+    if(safeContentsCtx->safeContentsDcx) {
+	SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsDcx);
+	safeContentsCtx->safeContentsDcx = NULL;
+    }
+}
+
+/* whenever a new safeContentsSafeBag is encountered, we need
+ * to init a safeContentsContext.  
+ */
+static SECStatus  
+sec_pkcs12_decoder_begin_nested_safe_contents(sec_PKCS12SafeContentsContext 
+							*safeContentsCtx)
+{
+    /* check for an error */
+    if(!safeContentsCtx || !safeContentsCtx->p12dcx || 
+		safeContentsCtx->p12dcx->error) {
+	return SECFailure;
+    }
+
+    safeContentsCtx->nestedCtx = sec_pkcs12_decoder_safe_contents_init_decode(
+						safeContentsCtx->p12dcx,
+						PR_TRUE);
+    if(!safeContentsCtx->nestedCtx) {
+	return SECFailure;
+    }
+
+    /* set up new filter proc */
+    SEC_ASN1DecoderSetNotifyProc(safeContentsCtx->nestedCtx->safeContentsDcx,
+				 sec_pkcs12_decoder_safe_contents_notify,
+				 safeContentsCtx->nestedCtx);
+    SEC_ASN1DecoderSetFilterProc(safeContentsCtx->currentSafeBagDcx,
+				 sec_pkcs12_decoder_nested_safe_contents_update,
+				 safeContentsCtx->nestedCtx, PR_TRUE);
+
+    return SECSuccess;
+}
+
+/* when the safeContents is done decoding, we need to reset the
+ * proper filter and notify procs and close the decoding context 
+ */
+static SECStatus
+sec_pkcs12_decoder_finish_nested_safe_contents(sec_PKCS12SafeContentsContext
+							*safeContentsCtx)
+{
+    /* check for error */
+    if(!safeContentsCtx || !safeContentsCtx->p12dcx || 
+		safeContentsCtx->p12dcx->error) {
+	return SECFailure;
+    }
+
+    /* clean up */	
+    SEC_ASN1DecoderClearFilterProc(safeContentsCtx->currentSafeBagDcx);
+    SEC_ASN1DecoderClearNotifyProc(safeContentsCtx->nestedCtx->safeContentsDcx);
+    SEC_ASN1DecoderFinish(safeContentsCtx->nestedCtx->safeContentsDcx);
+    safeContentsCtx->nestedCtx->safeContentsDcx = NULL;
+    safeContentsCtx->nestedCtx = NULL;
+
+    return SECSuccess;
+}
+
+/* wrapper for updating safeContents.  This is used when decoding
+ * the nested safeContents and any authenticatedSafes.
+ */
+static void
+sec_pkcs12_decoder_safe_contents_callback(void *arg, const char *buf,
+					  unsigned long len)
+{
+    SECStatus rv;
+    sec_PKCS12SafeContentsContext *safeContentsCtx = 
+        (sec_PKCS12SafeContentsContext *)arg;
+    SEC_PKCS12DecoderContext *p12dcx;
+
+    /* check for error */  
+    if(!safeContentsCtx || !safeContentsCtx->p12dcx 
+		|| safeContentsCtx->p12dcx->error) {
+	return;
+    }
+    p12dcx = safeContentsCtx->p12dcx;
+
+    /* update the decoder */
+    rv = SEC_ASN1DecoderUpdate(safeContentsCtx->safeContentsDcx, buf, len);
+    if(rv != SECSuccess) {
+	/* if we fail while trying to decode a 'safe', it's probably because
+	 * we didn't have the correct password. */
+	PORT_SetError(SEC_ERROR_BAD_PASSWORD);
+	p12dcx->errorValue = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE;
+	goto loser;
+    }
+
+    return;
+
+loser:
+    /* set the error and finish the context */
+    p12dcx->error = PR_TRUE;
+    if(safeContentsCtx->safeContentsDcx) {
+	SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsDcx);
+	safeContentsCtx->safeContentsDcx = NULL;
+    }
+
+    return;
+}
+
+/* this is a wrapper for the ASN1 decoder to call SEC_PKCS7DecoderUpdate
+ */
+static void
+sec_pkcs12_decoder_wrap_p7_update(void *arg, const char *data,
+				  unsigned long len, int depth,
+				  SEC_ASN1EncodingPart data_kind)
+{
+    SEC_PKCS7DecoderContext *p7dcx = (SEC_PKCS7DecoderContext *)arg;
+
+    SEC_PKCS7DecoderUpdate(p7dcx, data, len);
+}
+
+/* notify function for decoding aSafes.  at the beginning,
+ * of an authenticatedSafe, we start a decode of a safeContents.
+ * at the end, we clean up the safeContents decoder context and
+ * reset state variables 
+ */
+static void
+sec_pkcs12_decoder_asafes_notify(void *arg, PRBool before, void *dest, 
+			       int real_depth)
+{
+    SEC_PKCS12DecoderContext *p12dcx;
+    sec_PKCS12SafeContentsContext *safeContentsCtx;
+
+    /* make sure no error occurred. */
+    p12dcx = (SEC_PKCS12DecoderContext *)arg;
+    if(!p12dcx || p12dcx->error) {
+	return;
+    }
+
+    if(before) {
+
+	/* init a new safeContentsContext */
+	safeContentsCtx = sec_pkcs12_decoder_safe_contents_init_decode(p12dcx, 
+								PR_FALSE);
+	if(!safeContentsCtx) {
+	    goto loser;
+	}
+
+	/* set up password and encryption key information */
+	p12dcx->currentASafeKeyPwd = 
+	    (SEC_PKCS5KeyAndPassword*)PORT_ArenaZAlloc(p12dcx->arena, 
+					      sizeof(SEC_PKCS5KeyAndPassword));
+	if(!p12dcx->currentASafeKeyPwd) {
+	    p12dcx->errorValue = SEC_ERROR_NO_MEMORY;
+	    goto loser;
+	}
+	p12dcx->currentASafeKeyPwd->pwitem = p12dcx->pwitem;
+	p12dcx->currentASafeKeyPwd->slot = p12dcx->slot;
+	p12dcx->currentASafeKeyPwd->wincx = p12dcx->wincx;
+
+	/* initiate the PKCS7ContentInfo decode */
+	p12dcx->currentASafeP7Dcx = SEC_PKCS7DecoderStart(
+				sec_pkcs12_decoder_safe_contents_callback,
+				safeContentsCtx, 
+				p12dcx->pwfn, p12dcx->pwfnarg,
+				sec_pkcs12_decoder_get_decrypt_key, 
+				p12dcx->currentASafeKeyPwd, 
+				sec_pkcs12_decoder_decryption_allowed);
+	if(!p12dcx->currentASafeP7Dcx) {
+	    p12dcx->errorValue = PORT_GetError();
+	    goto loser;
+	}
+	SEC_ASN1DecoderSetFilterProc(p12dcx->aSafeDcx, 
+				     sec_pkcs12_decoder_wrap_p7_update,
+				     p12dcx->currentASafeP7Dcx, PR_TRUE);
+    }
+
+    if(!before) {
+	/* if one is being decoded, finish the decode */
+	if(p12dcx->currentASafeP7Dcx != NULL) {
+	    if(!SEC_PKCS7DecoderFinish(p12dcx->currentASafeP7Dcx)) {
+		p12dcx->currentASafeP7Dcx = NULL;
+		p12dcx->errorValue = PORT_GetError();
+		goto loser;
+	    }
+	    p12dcx->currentASafeP7Dcx = NULL;
+	}
+	p12dcx->currentASafeP7Dcx = NULL;
+	if(p12dcx->currentASafeKeyPwd->key != NULL) {
+	    p12dcx->currentASafeKeyPwd->key = NULL;
+	}
+    }
+
+
+    return;
+
+loser:
+    /* set the error flag */
+    p12dcx->error = PR_TRUE;
+    return;
+}
+
+/* wrapper for updating asafes decoding context.  this function
+ * writes data being decoded to disk, so that a mac can be computed
+ * later.  
+ */
+static void
+sec_pkcs12_decoder_asafes_callback(void *arg, const char *buf, 
+				  unsigned long len)
+{
+    SEC_PKCS12DecoderContext *p12dcx = (SEC_PKCS12DecoderContext *)arg;
+    SECStatus rv;
+
+    if(!p12dcx || p12dcx->error) {
+	return;
+    }
+
+    /* update the context */
+    rv = SEC_ASN1DecoderUpdate(p12dcx->aSafeDcx, buf, len);
+    if(rv != SECSuccess) {
+	p12dcx->error = (PRBool)SEC_ERROR_NO_MEMORY;
+	goto loser;
+    }
+
+    /* if we are writing to a file, write out the new information */
+    if(p12dcx->dWrite) {
+	unsigned long writeLen = (*p12dcx->dWrite)(p12dcx->dArg, 	
+						   (unsigned char *)buf, len);
+	if(writeLen != len) {
+	    p12dcx->errorValue = PORT_GetError();
+	    goto loser;
+	}
+    }
+
+    return;
+
+loser:
+    /* set the error flag */
+    p12dcx->error = PR_TRUE;
+    SEC_ASN1DecoderFinish(p12dcx->aSafeDcx);
+    p12dcx->aSafeDcx = NULL;
+
+    return;
+}
+   
+/* start the decode of an authenticatedSafe contentInfo.
+ */ 
+static SECStatus
+sec_pkcs12_decode_start_asafes_cinfo(SEC_PKCS12DecoderContext *p12dcx)
+{
+    if(!p12dcx || p12dcx->error) {
+	return SECFailure;
+    }
+
+    /* start the decode context */
+    p12dcx->aSafeDcx = SEC_ASN1DecoderStart(p12dcx->arena, 
+    					&p12dcx->authSafe,
+    					sec_PKCS12AuthenticatedSafeTemplate);
+    if(!p12dcx->aSafeDcx) {
+	p12dcx->errorValue = SEC_ERROR_NO_MEMORY;
+   	goto loser;
+    }
+
+    /* set the notify function */
+    SEC_ASN1DecoderSetNotifyProc(p12dcx->aSafeDcx,
+    				 sec_pkcs12_decoder_asafes_notify, p12dcx);
+
+    /* begin the authSafe decoder context */
+    p12dcx->aSafeP7Dcx = SEC_PKCS7DecoderStart(
+    				sec_pkcs12_decoder_asafes_callback, p12dcx,
+    				p12dcx->pwfn, p12dcx->pwfnarg, NULL, NULL, NULL);
+    if(!p12dcx->aSafeP7Dcx) {
+	p12dcx->errorValue = SEC_ERROR_NO_MEMORY;
+	goto loser;
+    }
+  
+    /* open the temp file for writing, if the filter functions were set */ 
+    if(p12dcx->dOpen && (*p12dcx->dOpen)(p12dcx->dArg, PR_FALSE) 
+				!= SECSuccess) {
+	p12dcx->errorValue = PORT_GetError();
+	goto loser;
+    }
+
+    return SECSuccess;
+
+loser:
+    p12dcx->error = PR_TRUE;
+
+    if(p12dcx->aSafeDcx) {
+	SEC_ASN1DecoderFinish(p12dcx->aSafeDcx);
+	p12dcx->aSafeDcx = NULL;
+    } 
+
+    if(p12dcx->aSafeP7Dcx) {
+	SEC_PKCS7DecoderFinish(p12dcx->aSafeP7Dcx);
+	p12dcx->aSafeP7Dcx = NULL;
+    }
+
+    return SECFailure;
+}
+
+/* wrapper for updating the safeContents.  this function is used as
+ * a filter for the pfx when decoding the authenticated safes 
+ */
+static void 
+sec_pkcs12_decode_asafes_cinfo_update(void *arg, const char *buf,
+				      unsigned long len, int depth,
+				      SEC_ASN1EncodingPart data_kind)
+{
+    SEC_PKCS12DecoderContext *p12dcx;
+    SECStatus rv;
+
+    p12dcx = (SEC_PKCS12DecoderContext*)arg;
+    if(!p12dcx || p12dcx->error) {
+	return;
+    }
+
+    /* update the safeContents decoder */
+    rv = SEC_PKCS7DecoderUpdate(p12dcx->aSafeP7Dcx, buf, len);
+    if(rv != SECSuccess) {
+	p12dcx->errorValue = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE;
+	goto loser;
+    }
+
+    return;
+
+loser:
+
+    /* did we find an error?  if so, close the context and set the 
+     * error flag.
+     */
+    SEC_PKCS7DecoderFinish(p12dcx->aSafeP7Dcx);
+    p12dcx->aSafeP7Dcx = NULL;
+    p12dcx->error = PR_TRUE;
+}
+
+/* notify procedure used while decoding the pfx.  When we encounter
+ * the authSafes, we want to trigger the decoding of authSafes as well
+ * as when we encounter the macData, trigger the decoding of it.  we do
+ * this because we we are streaming the decoder and not decoding in place.
+ * the pfx which is the destination, only has the version decoded into it.
+ */
+static void 
+sec_pkcs12_decoder_pfx_notify_proc(void *arg, PRBool before, void *dest,
+				   int real_depth)
+{
+    SECStatus rv;
+    SEC_PKCS12DecoderContext *p12dcx = (SEC_PKCS12DecoderContext*)arg;
+
+    /* if an error occurrs, clear the notifyProc and the filterProc 
+     * and continue. 
+     */
+    if(p12dcx->error) {
+	SEC_ASN1DecoderClearNotifyProc(p12dcx->pfxDcx);
+	SEC_ASN1DecoderClearFilterProc(p12dcx->pfxDcx);
+	return;
+    }
+
+    if(before && (dest == &p12dcx->pfx.encodedAuthSafe)) {
+
+	/* we want to make sure this is a version we support */
+	if(!sec_pkcs12_proper_version(&p12dcx->pfx)) {
+	    p12dcx->errorValue = SEC_ERROR_PKCS12_UNSUPPORTED_VERSION;
+	    goto loser;
+	}
+
+	/* start the decode of the aSafes cinfo... */
+	rv = sec_pkcs12_decode_start_asafes_cinfo(p12dcx);
+	if(rv != SECSuccess) {
+	    goto loser;
+	}
+
+	/* set the filter proc to update the authenticated safes. */
+	SEC_ASN1DecoderSetFilterProc(p12dcx->pfxDcx,
+				     sec_pkcs12_decode_asafes_cinfo_update,
+				     p12dcx, PR_TRUE);
+    }
+
+    if(!before && (dest == &p12dcx->pfx.encodedAuthSafe)) {
+
+	/* we are done decoding the authenticatedSafes, so we need to 
+	 * finish the decoderContext and clear the filter proc
+	 * and close the hmac callback, if present
+	 */
+	p12dcx->aSafeCinfo = SEC_PKCS7DecoderFinish(p12dcx->aSafeP7Dcx);
+	p12dcx->aSafeP7Dcx = NULL;
+	if(!p12dcx->aSafeCinfo) {
+	    p12dcx->errorValue = PORT_GetError();
+	    goto loser;
+	}
+	SEC_ASN1DecoderClearFilterProc(p12dcx->pfxDcx);
+	if(p12dcx->dClose && ((*p12dcx->dClose)(p12dcx->dArg, PR_FALSE) 
+				!= SECSuccess)) {
+	    p12dcx->errorValue = PORT_GetError();
+	    goto loser;
+	}
+
+    }
+
+    return;
+
+loser:
+    p12dcx->error = PR_TRUE;
+}
+
+/* SEC_PKCS12DecoderStart
+ *	Creates a decoder context for decoding a PKCS 12 PDU objct.
+ *	This function sets up the initial decoding context for the
+ *	PFX and sets the needed state variables.
+ *
+ *	pwitem - the password for the hMac and any encoded safes.
+ *		 this should be changed to take a callback which retrieves
+ *		 the password.  it may be possible for different safes to
+ *		 have different passwords.  also, the password is already
+ *		 in unicode.  it should probably be converted down below via
+ *		 a unicode conversion callback.
+ *	slot - the slot to import the dataa into should multiple slots 
+ *		 be supported based on key type and cert type?
+ *	dOpen, dClose, dRead, dWrite - digest routines for writing data
+ *		 to a file so it could be read back and the hmack recomputed
+ *		 and verified.  doesn't seem to be away for both encoding
+ *		 and decoding to be single pass, thus the need for these
+ *		 routines.
+ *	dArg - the argument for dOpen, etc.
+ *
+ *	This function returns the decoder context, if it was successful.
+ *	Otherwise, null is returned.
+ */
+SEC_PKCS12DecoderContext *
+SEC_PKCS12DecoderStart(SECItem *pwitem, PK11SlotInfo *slot, void *wincx,
+		       digestOpenFn dOpen, digestCloseFn dClose, 
+		       digestIOFn dRead, digestIOFn dWrite, void *dArg)
+{
+    SEC_PKCS12DecoderContext *p12dcx;
+    PRArenaPool *arena;
+
+    arena = PORT_NewArena(2048); /* different size? */
+    if(!arena) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	return NULL;
+    }
+
+    /* allocate the decoder context and set the state variables */
+    p12dcx = (SEC_PKCS12DecoderContext*)PORT_ArenaZAlloc(arena, sizeof(SEC_PKCS12DecoderContext));
+    if(!p12dcx) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+    p12dcx->arena = arena;
+    p12dcx->pwitem = pwitem;
+    p12dcx->slot = (slot ? slot : PK11_GetInternalKeySlot());
+    p12dcx->wincx = wincx;
+#ifdef IS_LITTLE_ENDIAN
+    p12dcx->swapUnicodeBytes = PR_TRUE;
+#else
+    p12dcx->swapUnicodeBytes = PR_FALSE;
+#endif
+    p12dcx->errorValue = 0;
+    p12dcx->error = PR_FALSE;
+
+    /* a slot is *required */
+    if(!slot) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	goto loser;
+    }
+
+    /* start the decoding of the PFX and set the notify proc
+     * for the PFX item.
+     */
+    p12dcx->pfxDcx = SEC_ASN1DecoderStart(p12dcx->arena, &p12dcx->pfx,
+    					  sec_PKCS12PFXItemTemplate);
+    if(!p12dcx->pfxDcx) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY); 
+	goto loser;
+    }
+
+    SEC_ASN1DecoderSetNotifyProc(p12dcx->pfxDcx, 
+				 sec_pkcs12_decoder_pfx_notify_proc,
+    				 p12dcx); 
+    
+    /* set up digest functions */
+    p12dcx->dOpen = dOpen;
+    p12dcx->dWrite = dWrite;
+    p12dcx->dClose = dClose;
+    p12dcx->dRead = dRead;
+    p12dcx->dArg = dArg;
+
+    return p12dcx;
+
+loser:
+    PORT_FreeArena(arena, PR_TRUE);
+    return NULL;
+}
+
+/* SEC_PKCS12DecoderUpdate 
+ *	Streaming update sending more data to the decoder.  If 
+ *	an error occurs, SECFailure is returned.
+ *
+ *	p12dcx - the decoder context 
+ *	data, len - the data buffer and length of data to send to 
+ *		the update functions.
+ */
+SECStatus
+SEC_PKCS12DecoderUpdate(SEC_PKCS12DecoderContext *p12dcx,
+			unsigned char *data, unsigned long len)
+{
+    SECStatus rv;
+
+    if(!p12dcx || p12dcx->error) {
+	return SECFailure;
+    }
+
+    /* update the PFX decoder context */
+    rv = SEC_ASN1DecoderUpdate(p12dcx->pfxDcx, (const char *)data, len);
+    if(rv != SECSuccess) {
+	p12dcx->errorValue = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE;
+	goto loser;
+    }
+
+    return SECSuccess;
+
+loser:
+
+    p12dcx->error = PR_TRUE;
+    return SECFailure;
+}
+
+/* IN_BUF_LEN should be larger than SHA1_LENGTH */
+#define IN_BUF_LEN		80
+
+/* verify the hmac by reading the data from the temporary file
+ * using the routines specified when the decodingContext was 
+ * created and return SECSuccess if the hmac matches.
+ */
+static SECStatus
+sec_pkcs12_decoder_verify_mac(SEC_PKCS12DecoderContext *p12dcx)
+{
+    SECStatus rv = SECFailure;
+    PBEBitGenContext *pbeCtxt = NULL;
+    SECItem *hmacKey = NULL, hmacRes;
+    unsigned char buf[IN_BUF_LEN];
+    unsigned int bufLen;
+    int iteration;
+    PK11Context *pk11cx;
+    SECOidTag algtag;
+    SECItem ignore = {0};
+    
+    if(!p12dcx || p12dcx->error) {
+	return SECFailure;
+    }
+
+    /* generate hmac key */
+    if(p12dcx->macData.iter.data) {
+	iteration = (int)DER_GetInteger(&p12dcx->macData.iter);
+    } else {
+	iteration = 1;
+    }
+    pbeCtxt = PBE_CreateContext(SECOID_GetAlgorithmTag(
+				&p12dcx->macData.safeMac.digestAlgorithm),
+				pbeBitGenIntegrityKey, p12dcx->pwitem,
+				&p12dcx->macData.macSalt, 160, iteration);
+    if(!pbeCtxt) {
+	return SECFailure;
+    }
+    hmacKey = PBE_GenerateBits(pbeCtxt);
+    PBE_DestroyContext(pbeCtxt);
+    pbeCtxt = NULL;
+    if(!hmacKey) {
+	return SECFailure;
+    }
+
+    /* init hmac */
+    algtag = SECOID_GetAlgorithmTag(&p12dcx->macData.safeMac.digestAlgorithm);
+    pk11cx = PK11_CreateContextByRawKey(NULL, 
+                                        sec_pkcs12_algtag_to_mech(algtag),
+                                        PK11_OriginDerive, CKA_SIGN, 
+                                        hmacKey, &ignore, NULL);
+    SECITEM_ZfreeItem(hmacKey, PR_TRUE);
+    hmacKey = NULL;
+    if(!pk11cx) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	return SECFailure;
+    }
+    rv = PK11_DigestBegin(pk11cx);
+
+    /* try to open the data for readback */
+    if(p12dcx->dOpen && ((*p12dcx->dOpen)(p12dcx->dArg, PR_TRUE) 
+			!= SECSuccess)) {
+	goto loser;
+    }
+
+    /* read the data back IN_BUF_LEN bytes at a time and recompute
+     * the hmac.  if fewer bytes are read than are requested, it is
+     * assumed that the end of file has been reached. if bytesRead
+     * is returned as -1, then an error occured reading from the 
+     * file.
+     */
+    while(1) {
+	int bytesRead = (*p12dcx->dRead)(p12dcx->dArg, buf, IN_BUF_LEN);
+	if(bytesRead == -1) {
+	    goto loser;
+	}
+
+	rv = PK11_DigestOp(pk11cx, buf, bytesRead);
+	if(bytesRead < IN_BUF_LEN) {
+	    break;
+	}
+    }
+
+    /* finish the hmac context */
+    rv = PK11_DigestFinal(pk11cx, buf, &bufLen, IN_BUF_LEN);
+
+    hmacRes.data = buf;
+    hmacRes.len = bufLen;
+
+    /* is the hmac computed the same as the hmac which was decoded? */
+    rv = SECSuccess;
+    if(SECITEM_CompareItem(&hmacRes, &p12dcx->macData.safeMac.digest) 
+			!= SECEqual) {
+	PORT_SetError(SEC_ERROR_PKCS12_INVALID_MAC);
+	rv = SECFailure;
+    }
+
+loser:
+    /* close the file and remove it */
+    if(p12dcx->dClose) {
+	(*p12dcx->dClose)(p12dcx->dArg, PR_TRUE);
+    }
+
+    if(pk11cx) {
+	PK11_DestroyContext(pk11cx, PR_TRUE);
+    }
+
+    if(hmacKey) {
+	SECITEM_ZfreeItem(hmacKey, PR_TRUE);
+    }
+
+    return rv;
+}
+
+/* SEC_PKCS12DecoderVerify
+ * 	Verify the macData or the signature of the decoded PKCS 12 PDU.
+ *	If the signature or the macData do not match, SECFailure is
+ *	returned.
+ *
+ * 	p12dcx - the decoder context 
+ */
+SECStatus
+SEC_PKCS12DecoderVerify(SEC_PKCS12DecoderContext *p12dcx)
+{
+    SECStatus rv = SECSuccess;
+
+    /* make sure that no errors have occured... */
+    if(!p12dcx || p12dcx->error) {
+	return SECFailure;
+    }
+
+    /* check the signature or the mac depending on the type of
+     * integrity used.
+     */
+    if(p12dcx->pfx.encodedMacData.len) {
+	rv = SEC_ASN1DecodeItem(p12dcx->arena, &p12dcx->macData,
+				sec_PKCS12MacDataTemplate,
+				&p12dcx->pfx.encodedMacData);
+	if(rv == SECSuccess) {
+	    return sec_pkcs12_decoder_verify_mac(p12dcx);
+	} else {
+	    PORT_SetError(SEC_ERROR_NO_MEMORY);
+	}
+    } else {
+	if(SEC_PKCS7VerifySignature(p12dcx->aSafeCinfo, certUsageEmailSigner,
+				    PR_FALSE)) {
+	    return SECSuccess;
+	} else {
+	    PORT_SetError(SEC_ERROR_PKCS12_INVALID_MAC);
+	}
+    }
+
+    return SECFailure;
+}
+
+/* SEC_PKCS12DecoderFinish
+ *	Free any open ASN1 or PKCS7 decoder contexts and then
+ *	free the arena pool which everything should be allocated
+ *	from.  This function should be called upon completion of
+ *	decoding and installing of a pfx pdu.  This should be
+ *	called even if an error occurs.
+ *
+ *	p12dcx - the decoder context
+ */
+void
+SEC_PKCS12DecoderFinish(SEC_PKCS12DecoderContext *p12dcx)
+{
+    void *freedCtxt = NULL;
+
+    if(!p12dcx) {
+	return;
+    }
+
+    if(p12dcx->pfxDcx) {
+	SEC_ASN1DecoderFinish(p12dcx->pfxDcx);
+	p12dcx->pfxDcx = NULL;
+    }
+
+    if(p12dcx->aSafeDcx) {
+	SEC_ASN1DecoderFinish(p12dcx->aSafeDcx);
+	p12dcx->aSafeDcx = NULL;
+    }
+
+    if(p12dcx->currentASafeP7Dcx) {
+	SEC_PKCS7DecoderFinish(p12dcx->currentASafeP7Dcx);
+	p12dcx->currentASafeP7Dcx = NULL;
+    }
+
+    if(p12dcx->aSafeP7Dcx) {
+	SEC_PKCS7DecoderFinish(p12dcx->aSafeP7Dcx);
+    }
+
+    if(p12dcx->hmacDcx) {
+	SEC_ASN1DecoderFinish(p12dcx->hmacDcx);
+	p12dcx->hmacDcx = NULL;
+    }
+
+    if(p12dcx->arena) {
+	PORT_FreeArena(p12dcx->arena, PR_TRUE);
+    }
+}
+
+static SECStatus
+sec_pkcs12_decoder_set_attribute_value(sec_PKCS12SafeBag *bag,
+			       SECOidTag attributeType,
+			       SECItem *attrValue)
+{
+    int i = 0;
+    SECOidData *oid;
+
+    if(!bag || !attrValue) {
+	return SECFailure;
+    }
+
+    oid = SECOID_FindOIDByTag(attributeType);
+    if(!oid) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	return SECFailure;
+    }
+
+    if(!bag->attribs) {
+	bag->attribs = (sec_PKCS12Attribute**)PORT_ArenaZAlloc(bag->arena, 
+					sizeof(sec_PKCS12Attribute *) * 2);
+    } else {
+	while(bag->attribs[i]) i++;
+	bag->attribs = (sec_PKCS12Attribute **)PORT_ArenaGrow(bag->arena, 
+				      bag->attribs, 
+				      (i + 1) * sizeof(sec_PKCS12Attribute *),
+				      (i + 2) * sizeof(sec_PKCS12Attribute *));
+    }
+
+    if(!bag->attribs) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	return SECFailure;
+    }
+
+    bag->attribs[i] = (sec_PKCS12Attribute*)PORT_ArenaZAlloc(bag->arena, 
+						  sizeof(sec_PKCS12Attribute));
+    if(!bag->attribs) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	return SECFailure;
+    }
+
+    bag->attribs[i]->attrValue = (SECItem**)PORT_ArenaZAlloc(bag->arena, 
+						  sizeof(SECItem *) * 2);
+    if(!bag->attribs[i]->attrValue) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	return SECFailure;
+    }
+
+    bag->attribs[i+1] = NULL;
+    bag->attribs[i]->attrValue[0] = attrValue;
+    bag->attribs[i]->attrValue[1] = NULL;
+
+    if(SECITEM_CopyItem(bag->arena, &bag->attribs[i]->attrType, &oid->oid)
+			!= SECSuccess) {
+	PORT_SetError(SEC_ERROR_NO_MEMORY);
+	return SECFailure;
+    }
+
+    return SECSuccess;
+}
+
+static SECItem *
+sec_pkcs12_get_attribute_value(sec_PKCS12SafeBag *bag,
+			       SECOidTag attributeType)
+{
+    int i = 0;
+
+    if(!bag->attribs) {
+	return NULL;
+    }
+
+    while(bag->attribs[i] != NULL) {
+	if(SECOID_FindOIDTag(&bag->attribs[i]->attrType) 
+			== attributeType) {
+	    return bag->attribs[i]->attrValue[0];
+	}
+	i++;
+    }
+
+    return NULL;
+}
+
+/* For now, this function will merely remove any ":"
+ * in the nickname which the PK11 functions may have
+ * placed there.  This will keep dual certs from appearing
+ * twice under "Your" certificates when imported onto smart
+ * cards.  Once with the name "Slot:Cert" and another with
+ * the nickname "Slot:Slot:Cert"
+ */
+static void
+sec_pkcs12_sanitize_nickname(PK11SlotInfo *slot, SECItem *nick)
+{
+    char *nickname;
+    char *delimit;
+    int delimitlen;
+ 
+    nickname = (char*)nick->data; /*Mac breaks without this type cast*/
+    if ((delimit = PORT_Strchr(nickname, ':')) != NULL) {
+        char *slotName;
+	int slotNameLen;
+
+	slotNameLen = delimit-nickname;
+	slotName = PORT_NewArray(char, (slotNameLen+1));
+	PORT_Assert(slotName);
+	if (slotName == NULL) {
+	  /* What else can we do?*/
+	  return;
+	}
+	PORT_Memcpy(slotName, nickname, slotNameLen);
+	slotName[slotNameLen] = '\0';
+	if (PORT_Strcmp(PK11_GetTokenName(slot), slotName) == 0) {
+	  delimitlen = PORT_Strlen(delimit+1);
+	  PORT_Memmove(nickname, delimit+1, delimitlen+1);
+	  nick->len = delimitlen;
+	}
+	PORT_Free(slotName);
+    }
+ 
+}
+
+static SECItem *
+sec_pkcs12_get_nickname(sec_PKCS12SafeBag *bag)
+{
+    SECItem *src, *dest;
+
+    if(!bag) {
+	bag->problem = PR_TRUE;
+	bag->error = SEC_ERROR_NO_MEMORY;
+	return NULL;
+    }
+
+    src = sec_pkcs12_get_attribute_value(bag, SEC_OID_PKCS9_FRIENDLY_NAME);
+    if(!src) {
+	return NULL;
+    }
+
+    dest = (SECItem*)PORT_ZAlloc(sizeof(SECItem));
+    if(!dest) { 
+	goto loser;
+    }
+    if(!sec_pkcs12_convert_item_to_unicode(NULL, dest, src, PR_FALSE, 
+				PR_FALSE, PR_FALSE)) {
+	goto loser;
+    }
+
+    sec_pkcs12_sanitize_nickname(bag->slot, dest);
+
+    return dest;
+
+loser:
+    if(dest) {
+	SECITEM_ZfreeItem(dest, PR_TRUE);
+    }
+
+    bag->problem = PR_TRUE;
+    bag->error = PORT_GetError();
+    return NULL;
+}
+
+static SECStatus
+sec_pkcs12_set_nickname(sec_PKCS12SafeBag *bag, SECItem *name)
+{
+    int i = 0;
+    sec_PKCS12Attribute *attr = NULL;
+    SECOidData *oid = SECOID_FindOIDByTag(SEC_OID_PKCS9_FRIENDLY_NAME);
+
+    if(!bag || !bag->arena || !name) {
+	return SECFailure;
+    }
+	
+    if(!bag->attribs) {
+	if(!oid) {
+	    goto loser;
+	}
+
+	bag->attribs = (sec_PKCS12Attribute**)PORT_ArenaZAlloc(bag->arena, 
+					     sizeof(sec_PKCS12Attribute *)*2);
+	if(!bag->attribs) {
+	    goto loser;
+	}
+	bag->attribs[0] = (sec_PKCS12Attribute*)PORT_ArenaZAlloc(bag->arena, 
+						  sizeof(sec_PKCS12Attribute));
+	if(!bag->attribs[0]) {
+	    goto loser;
+	}
+	bag->attribs[1] = NULL;
+
+	attr = bag->attribs[0];
+	if(SECITEM_CopyItem(bag->arena, &attr->attrType, &oid->oid) 
+			!= SECSuccess) {
+	    goto loser;
+	}
+    } else {
+	while(bag->attribs[i]) {
+	    if(SECOID_FindOIDTag(&ba