diff options
Diffstat (limited to '')
-rw-r--r-- | security/nss/lib/softoken/legacydb/keydb.c | 2274 |
1 files changed, 2274 insertions, 0 deletions
diff --git a/security/nss/lib/softoken/legacydb/keydb.c b/security/nss/lib/softoken/legacydb/keydb.c new file mode 100644 index 0000000000..22ab1cc0ef --- /dev/null +++ b/security/nss/lib/softoken/legacydb/keydb.c @@ -0,0 +1,2274 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "lowkeyi.h" +#include "secasn1.h" +#include "secder.h" +#include "secoid.h" +#include "blapi.h" +#include "secitem.h" +#include "pcert.h" +#include "mcom_db.h" +#include "secerr.h" + +#include "keydbi.h" +#include "lgdb.h" + +/* + * Record keys for keydb + */ +#define SALT_STRING "global-salt" +#define VERSION_STRING "Version" +#define KEYDB_PW_CHECK_STRING "password-check" +#define KEYDB_PW_CHECK_LEN 14 +#define KEYDB_FAKE_PW_CHECK_STRING "fake-password-check" +#define KEYDB_FAKE_PW_CHECK_LEN 19 + +/* Size of the global salt for key database */ +#define SALT_LENGTH 16 + +SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate) + +const SEC_ASN1Template nsslowkey_EncryptedPrivateKeyInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSLOWKEYEncryptedPrivateKeyInfo) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, + offsetof(NSSLOWKEYEncryptedPrivateKeyInfo, algorithm), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_OCTET_STRING, + offsetof(NSSLOWKEYEncryptedPrivateKeyInfo, encryptedData) }, + { 0 } +}; + +const SEC_ASN1Template nsslowkey_PointerToEncryptedPrivateKeyInfoTemplate[] = { + { SEC_ASN1_POINTER, 0, nsslowkey_EncryptedPrivateKeyInfoTemplate } +}; + +/* ====== Default key databse encryption algorithm ====== */ +static void +sec_destroy_dbkey(NSSLOWKEYDBKey *dbkey) +{ + if (dbkey && dbkey->arena) { + PORT_FreeArena(dbkey->arena, PR_FALSE); + } +} + +static void +free_dbt(DBT *dbt) +{ + if (dbt) { + PORT_Free(dbt->data); + PORT_Free(dbt); + } + + return; +} + +static int keydb_Get(NSSLOWKEYDBHandle *db, DBT *key, DBT *data, + unsigned int flags); +static int keydb_Put(NSSLOWKEYDBHandle *db, DBT *key, DBT *data, + unsigned int flags); +static int keydb_Sync(NSSLOWKEYDBHandle *db, unsigned int flags); +static int keydb_Del(NSSLOWKEYDBHandle *db, DBT *key, unsigned int flags); +static int keydb_Seq(NSSLOWKEYDBHandle *db, DBT *key, DBT *data, + unsigned int flags); +static void keydb_Close(NSSLOWKEYDBHandle *db); + +/* + * format of key database entries for version 3 of database: + * byte offset field + * ----------- ----- + * 0 version + * 1 salt-len + * 2 nn-len + * 3.. salt-data + * ... nickname + * ... encrypted-key-data + */ +static DBT * +encode_dbkey(NSSLOWKEYDBKey *dbkey, unsigned char version) +{ + DBT *bufitem = NULL; + unsigned char *buf; + int nnlen; + char *nn; + + bufitem = (DBT *)PORT_ZAlloc(sizeof(DBT)); + if (bufitem == NULL) { + goto loser; + } + + if (dbkey->nickname) { + nn = dbkey->nickname; + nnlen = PORT_Strlen(nn) + 1; + } else { + nn = ""; + nnlen = 1; + } + + /* compute the length of the record */ + /* 1 + 1 + 1 == version number header + salt length + nn len */ + bufitem->size = dbkey->salt.len + nnlen + dbkey->derPK.len + 1 + 1 + 1; + + bufitem->data = (void *)PORT_ZAlloc(bufitem->size); + if (bufitem->data == NULL) { + goto loser; + } + + buf = (unsigned char *)bufitem->data; + + /* set version number */ + buf[0] = version; + + /* set length of salt */ + PORT_Assert(dbkey->salt.len < 256); + buf[1] = dbkey->salt.len; + + /* set length of nickname */ + PORT_Assert(nnlen < 256); + buf[2] = nnlen; + + /* copy salt */ + if (dbkey->salt.len > 0) { + PORT_Memcpy(&buf[3], dbkey->salt.data, dbkey->salt.len); + } + + /* copy nickname */ + PORT_Memcpy(&buf[3 + dbkey->salt.len], nn, nnlen); + + /* copy encrypted key */ + PORT_Memcpy(&buf[3 + dbkey->salt.len + nnlen], dbkey->derPK.data, + dbkey->derPK.len); + + return (bufitem); + +loser: + if (bufitem) { + free_dbt(bufitem); + } + + return (NULL); +} + +static NSSLOWKEYDBKey * +decode_dbkey(DBT *bufitem, int expectedVersion) +{ + NSSLOWKEYDBKey *dbkey; + PLArenaPool *arena = NULL; + unsigned char *buf; + int version; + int keyoff; + int nnlen; + int saltoff; + + buf = (unsigned char *)bufitem->data; + + version = buf[0]; + + if (version != expectedVersion) { + goto loser; + } + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + goto loser; + } + + dbkey = (NSSLOWKEYDBKey *)PORT_ArenaZAlloc(arena, sizeof(NSSLOWKEYDBKey)); + if (dbkey == NULL) { + goto loser; + } + + dbkey->arena = arena; + dbkey->salt.data = NULL; + dbkey->derPK.data = NULL; + + dbkey->salt.len = buf[1]; + dbkey->salt.data = (unsigned char *)PORT_ArenaZAlloc(arena, dbkey->salt.len); + if (dbkey->salt.data == NULL) { + goto loser; + } + + saltoff = 2; + keyoff = 2 + dbkey->salt.len; + + if (expectedVersion >= 3) { + nnlen = buf[2]; + if (nnlen) { + dbkey->nickname = (char *)PORT_ArenaZAlloc(arena, nnlen + 1); + if (dbkey->nickname) { + PORT_Memcpy(dbkey->nickname, &buf[keyoff + 1], nnlen); + } + } + keyoff += (nnlen + 1); + saltoff = 3; + } + + PORT_Memcpy(dbkey->salt.data, &buf[saltoff], dbkey->salt.len); + + dbkey->derPK.len = bufitem->size - keyoff; + dbkey->derPK.data = (unsigned char *)PORT_ArenaZAlloc(arena, dbkey->derPK.len); + if (dbkey->derPK.data == NULL) { + goto loser; + } + + PORT_Memcpy(dbkey->derPK.data, &buf[keyoff], dbkey->derPK.len); + + return (dbkey); + +loser: + + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + + return (NULL); +} + +static NSSLOWKEYDBKey * +get_dbkey(NSSLOWKEYDBHandle *handle, DBT *index) +{ + NSSLOWKEYDBKey *dbkey; + DBT entry; + int ret; + + /* get it from the database */ + ret = keydb_Get(handle, index, &entry, 0); + if (ret) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + return NULL; + } + + /* set up dbkey struct */ + + dbkey = decode_dbkey(&entry, handle->version); + + return (dbkey); +} + +static SECStatus +put_dbkey(NSSLOWKEYDBHandle *handle, DBT *index, NSSLOWKEYDBKey *dbkey, PRBool update) +{ + DBT *keydata = NULL; + int status; + + keydata = encode_dbkey(dbkey, handle->version); + if (keydata == NULL) { + goto loser; + } + + /* put it in the database */ + if (update) { + status = keydb_Put(handle, index, keydata, 0); + } else { + status = keydb_Put(handle, index, keydata, R_NOOVERWRITE); + } + + if (status) { + goto loser; + } + + /* sync the database */ + status = keydb_Sync(handle, 0); + if (status) { + goto loser; + } + + free_dbt(keydata); + return (SECSuccess); + +loser: + if (keydata) { + free_dbt(keydata); + } + + return (SECFailure); +} + +SECStatus +nsslowkey_TraverseKeys(NSSLOWKEYDBHandle *handle, + SECStatus (*keyfunc)(DBT *k, DBT *d, void *pdata), + void *udata) +{ + DBT data; + DBT key; + SECStatus status; + int ret; + + if (handle == NULL) { + return (SECFailure); + } + + ret = keydb_Seq(handle, &key, &data, R_FIRST); + if (ret) { + return (SECFailure); + } + + do { + /* skip version record */ + if (data.size > 1) { + if (key.size == (sizeof(SALT_STRING) - 1)) { + if (PORT_Memcmp(key.data, SALT_STRING, key.size) == 0) { + continue; + } + } + + /* skip password check */ + if (key.size == KEYDB_PW_CHECK_LEN) { + if (PORT_Memcmp(key.data, KEYDB_PW_CHECK_STRING, + KEYDB_PW_CHECK_LEN) == 0) { + continue; + } + } + + status = (*keyfunc)(&key, &data, udata); + if (status != SECSuccess) { + return (status); + } + } + } while (keydb_Seq(handle, &key, &data, R_NEXT) == 0); + + return (SECSuccess); +} + +#ifdef notdef +typedef struct keyNode { + struct keyNode *next; + DBT key; +} keyNode; + +typedef struct { + PLArenaPool *arena; + keyNode *head; +} keyList; + +static SECStatus +sec_add_key_to_list(DBT *key, DBT *data, void *arg) +{ + keyList *keylist; + keyNode *node; + void *keydata; + + keylist = (keyList *)arg; + + /* allocate the node struct */ + node = (keyNode *)PORT_ArenaZAlloc(keylist->arena, sizeof(keyNode)); + if (node == NULL) { + return (SECFailure); + } + + /* allocate room for key data */ + keydata = PORT_ArenaZAlloc(keylist->arena, key->size); + if (keydata == NULL) { + return (SECFailure); + } + + /* link node into list */ + node->next = keylist->head; + keylist->head = node; + + /* copy key into node */ + PORT_Memcpy(keydata, key->data, key->size); + node->key.size = key->size; + node->key.data = keydata; + + return (SECSuccess); +} +#endif + +static SECItem * +decodeKeyDBGlobalSalt(DBT *saltData) +{ + SECItem *saltitem; + + saltitem = (SECItem *)PORT_ZAlloc(sizeof(SECItem)); + if (saltitem == NULL) { + return (NULL); + } + + saltitem->data = (unsigned char *)PORT_ZAlloc(saltData->size); + if (saltitem->data == NULL) { + PORT_Free(saltitem); + return (NULL); + } + + saltitem->len = saltData->size; + PORT_Memcpy(saltitem->data, saltData->data, saltitem->len); + + return (saltitem); +} + +static SECItem * +GetKeyDBGlobalSalt(NSSLOWKEYDBHandle *handle) +{ + DBT saltKey; + DBT saltData; + int ret; + + saltKey.data = SALT_STRING; + saltKey.size = sizeof(SALT_STRING) - 1; + + ret = keydb_Get(handle, &saltKey, &saltData, 0); + if (ret) { + return (NULL); + } + + return (decodeKeyDBGlobalSalt(&saltData)); +} + +static SECStatus +StoreKeyDBGlobalSalt(NSSLOWKEYDBHandle *handle, SECItem *salt) +{ + DBT saltKey; + DBT saltData; + int status; + + saltKey.data = SALT_STRING; + saltKey.size = sizeof(SALT_STRING) - 1; + + saltData.data = (void *)salt->data; + saltData.size = salt->len; + + /* put global salt into the database now */ + status = keydb_Put(handle, &saltKey, &saltData, 0); + if (status) { + return (SECFailure); + } + + return (SECSuccess); +} + +static SECStatus +makeGlobalVersion(NSSLOWKEYDBHandle *handle) +{ + unsigned char version; + DBT versionData; + DBT versionKey; + int status; + + version = NSSLOWKEY_DB_FILE_VERSION; + versionData.data = &version; + versionData.size = 1; + versionKey.data = VERSION_STRING; + versionKey.size = sizeof(VERSION_STRING) - 1; + + /* put version string into the database now */ + status = keydb_Put(handle, &versionKey, &versionData, 0); + if (status) { + return (SECFailure); + } + handle->version = version; + + return (SECSuccess); +} + +static SECStatus +makeGlobalSalt(NSSLOWKEYDBHandle *handle) +{ + DBT saltKey; + DBT saltData; + unsigned char saltbuf[16]; + int status; + + saltKey.data = SALT_STRING; + saltKey.size = sizeof(SALT_STRING) - 1; + + saltData.data = (void *)saltbuf; + saltData.size = sizeof(saltbuf); + RNG_GenerateGlobalRandomBytes(saltbuf, sizeof(saltbuf)); + + /* put global salt into the database now */ + status = keydb_Put(handle, &saltKey, &saltData, 0); + if (status) { + return (SECFailure); + } + + return (SECSuccess); +} + +static SECStatus +encodePWCheckEntry(PLArenaPool *arena, SECItem *entry, SECOidTag alg, + SECItem *encCheck); + +static unsigned char +nsslowkey_version(NSSLOWKEYDBHandle *handle) +{ + DBT versionKey; + DBT versionData; + int ret; + versionKey.data = VERSION_STRING; + versionKey.size = sizeof(VERSION_STRING) - 1; + + if (handle->db == NULL) { + return 255; + } + + /* lookup version string in database */ + ret = keydb_Get(handle, &versionKey, &versionData, 0); + + /* error accessing the database */ + if (ret < 0) { + return 255; + } + + if (ret >= 1) { + return 0; + } + return *((unsigned char *)versionData.data); +} + +static PRBool +seckey_HasAServerKey(NSSLOWKEYDBHandle *handle) +{ + DBT key; + DBT data; + int ret; + PRBool found = PR_FALSE; + + ret = keydb_Seq(handle, &key, &data, R_FIRST); + if (ret) { + return PR_FALSE; + } + + do { + /* skip version record */ + if (data.size > 1) { + /* skip salt */ + if (key.size == (sizeof(SALT_STRING) - 1)) { + if (PORT_Memcmp(key.data, SALT_STRING, key.size) == 0) { + continue; + } + } + /* skip pw check entry */ + if (key.size == KEYDB_PW_CHECK_LEN) { + if (PORT_Memcmp(key.data, KEYDB_PW_CHECK_STRING, + KEYDB_PW_CHECK_LEN) == 0) { + continue; + } + } + + /* keys stored by nickname will have 0 as the last byte of the + * db key. Other keys must be stored by modulus. We will not + * update those because they are left over from a keygen that + * never resulted in a cert. + */ + if (((unsigned char *)key.data)[key.size - 1] != 0) { + continue; + } + + if (PORT_Strcmp(key.data, "Server-Key") == 0) { + found = PR_TRUE; + break; + } + } + } while (keydb_Seq(handle, &key, &data, R_NEXT) == 0); + + return found; +} + +/* forward declare local create function */ +static NSSLOWKEYDBHandle *nsslowkey_NewHandle(DB *dbHandle); + +/* + * currently updates key database from v2 to v3 + */ +static SECStatus +nsslowkey_UpdateKeyDBPass1(NSSLOWKEYDBHandle *handle) +{ + SECStatus rv; + DBT checkKey; + DBT checkData; + DBT saltKey; + DBT saltData; + DBT key; + DBT data; + unsigned char version; + NSSLOWKEYDBKey *dbkey = NULL; + NSSLOWKEYDBHandle *update = NULL; + SECItem *oldSalt = NULL; + int ret; + SECItem checkitem; + + if (handle->updatedb == NULL) { + return SECSuccess; + } + + /* create a full DB Handle for our update so we + * can use the correct locks for the db primatives */ + update = nsslowkey_NewHandle(handle->updatedb); + if (update == NULL) { + return SECSuccess; + } + + /* update has now inherited the database handle */ + handle->updatedb = NULL; + + /* + * check the version record + */ + version = nsslowkey_version(update); + if (version != 2) { + goto done; + } + + saltKey.data = SALT_STRING; + saltKey.size = sizeof(SALT_STRING) - 1; + + ret = keydb_Get(update, &saltKey, &saltData, 0); + if (ret) { + /* no salt in old db, so it is corrupted */ + goto done; + } + + oldSalt = decodeKeyDBGlobalSalt(&saltData); + if (oldSalt == NULL) { + /* bad salt in old db, so it is corrupted */ + goto done; + } + + /* + * look for a pw check entry + */ + checkKey.data = KEYDB_PW_CHECK_STRING; + checkKey.size = KEYDB_PW_CHECK_LEN; + + ret = keydb_Get(update, &checkKey, &checkData, 0); + if (ret) { + /* + * if we have a key, but no KEYDB_PW_CHECK_STRING, then this must + * be an old server database, and it does have a password associated + * with it. Put a fake entry in so we can identify this db when we do + * get the password for it. + */ + if (seckey_HasAServerKey(update)) { + DBT fcheckKey; + DBT fcheckData; + + /* + * include a fake string + */ + fcheckKey.data = KEYDB_FAKE_PW_CHECK_STRING; + fcheckKey.size = KEYDB_FAKE_PW_CHECK_LEN; + fcheckData.data = "1"; + fcheckData.size = 1; + /* put global salt into the new database now */ + ret = keydb_Put(handle, &saltKey, &saltData, 0); + if (ret) { + goto done; + } + ret = keydb_Put(handle, &fcheckKey, &fcheckData, 0); + if (ret) { + goto done; + } + } else { + goto done; + } + } else { + /* put global salt into the new database now */ + ret = keydb_Put(handle, &saltKey, &saltData, 0); + if (ret) { + goto done; + } + + dbkey = decode_dbkey(&checkData, 2); + if (dbkey == NULL) { + goto done; + } + checkitem = dbkey->derPK; + dbkey->derPK.data = NULL; + + /* format the new pw check entry */ + rv = encodePWCheckEntry(NULL, &dbkey->derPK, SEC_OID_RC4, &checkitem); + if (rv != SECSuccess) { + goto done; + } + + rv = put_dbkey(handle, &checkKey, dbkey, PR_TRUE); + if (rv != SECSuccess) { + goto done; + } + + /* free the dbkey */ + sec_destroy_dbkey(dbkey); + dbkey = NULL; + } + + /* now traverse the database */ + ret = keydb_Seq(update, &key, &data, R_FIRST); + if (ret) { + goto done; + } + + do { + /* skip version record */ + if (data.size > 1) { + /* skip salt */ + if (key.size == (sizeof(SALT_STRING) - 1)) { + if (PORT_Memcmp(key.data, SALT_STRING, key.size) == 0) { + continue; + } + } + /* skip pw check entry */ + if (key.size == checkKey.size) { + if (PORT_Memcmp(key.data, checkKey.data, key.size) == 0) { + continue; + } + } + + /* keys stored by nickname will have 0 as the last byte of the + * db key. Other keys must be stored by modulus. We will not + * update those because they are left over from a keygen that + * never resulted in a cert. + */ + if (((unsigned char *)key.data)[key.size - 1] != 0) { + continue; + } + + dbkey = decode_dbkey(&data, 2); + if (dbkey == NULL) { + continue; + } + + /* This puts the key into the new database with the same + * index (nickname) that it had before. The second pass + * of the update will have the password. It will decrypt + * and re-encrypt the entries using a new algorithm. + */ + dbkey->nickname = (char *)key.data; + rv = put_dbkey(handle, &key, dbkey, PR_FALSE); + dbkey->nickname = NULL; + + sec_destroy_dbkey(dbkey); + } + } while (keydb_Seq(update, &key, &data, R_NEXT) == 0); + + dbkey = NULL; + +done: + /* sync the database */ + ret = keydb_Sync(handle, 0); + + nsslowkey_CloseKeyDB(update); + + if (oldSalt) { + SECITEM_FreeItem(oldSalt, PR_TRUE); + } + + if (dbkey) { + sec_destroy_dbkey(dbkey); + } + + return (SECSuccess); +} + +static SECStatus +openNewDB(const char *appName, const char *prefix, const char *dbname, + NSSLOWKEYDBHandle *handle, NSSLOWKEYDBNameFunc namecb, void *cbarg) +{ + SECStatus rv = SECFailure; + int status = RDB_FAIL; + char *updname = NULL; + DB *updatedb = NULL; + PRBool updated = PR_FALSE; + int ret; + + if (appName) { + handle->db = rdbopen(appName, prefix, "key", NO_CREATE, &status); + } else { + handle->db = dbopen(dbname, NO_CREATE, 0600, DB_HASH, 0); + } + /* if create fails then we lose */ + if (handle->db == NULL) { + return (status == RDB_RETRY) ? SECWouldBlock : SECFailure; + } + + /* force a transactional read, which will verify that one and only one + * process attempts the update. */ + if (nsslowkey_version(handle) == NSSLOWKEY_DB_FILE_VERSION) { + /* someone else has already updated the database for us */ + db_InitComplete(handle->db); + return SECSuccess; + } + + /* + * if we are creating a multiaccess database, see if there is a + * local database we can update from. + */ + if (appName) { + NSSLOWKEYDBHandle *updateHandle; + updatedb = dbopen(dbname, NO_RDONLY, 0600, DB_HASH, 0); + if (!updatedb) { + goto noupdate; + } + + /* nsslowkey_version needs a full handle because it calls + * the kdb_Get() function, which needs to lock. + */ + updateHandle = nsslowkey_NewHandle(updatedb); + if (!updateHandle) { + updatedb->close(updatedb); + goto noupdate; + } + + handle->version = nsslowkey_version(updateHandle); + if (handle->version != NSSLOWKEY_DB_FILE_VERSION) { + nsslowkey_CloseKeyDB(updateHandle); + goto noupdate; + } + + /* copy the new DB from the old one */ + db_Copy(handle->db, updatedb); + nsslowkey_CloseKeyDB(updateHandle); + db_InitComplete(handle->db); + return SECSuccess; + } +noupdate: + + /* update the version number */ + rv = makeGlobalVersion(handle); + if (rv != SECSuccess) { + goto loser; + } + + /* + * try to update from v2 db + */ + updname = (*namecb)(cbarg, 2); + if (updname != NULL) { + handle->updatedb = dbopen(updname, NO_RDONLY, 0600, DB_HASH, 0); + PORT_Free(updname); + + if (handle->updatedb) { + /* + * Try to update the db using a null password. If the db + * doesn't have a password, then this will work. If it does + * have a password, then this will fail and we will do the + * update later + */ + rv = nsslowkey_UpdateKeyDBPass1(handle); + if (rv == SECSuccess) { + updated = PR_TRUE; + } + } + } + + /* we are using the old salt if we updated from an old db */ + if (!updated) { + rv = makeGlobalSalt(handle); + if (rv != SECSuccess) { + goto loser; + } + } + + /* sync the database */ + ret = keydb_Sync(handle, 0); + if (ret) { + rv = SECFailure; + goto loser; + } + rv = SECSuccess; + +loser: + db_InitComplete(handle->db); + return rv; +} + +static DB * +openOldDB(const char *appName, const char *prefix, const char *dbname, + PRBool openflags) +{ + DB *db = NULL; + + if (appName) { + db = rdbopen(appName, prefix, "key", openflags, NULL); + } else { + db = dbopen(dbname, openflags, 0600, DB_HASH, 0); + } + + return db; +} + +/* check for correct version number */ +static PRBool +verifyVersion(NSSLOWKEYDBHandle *handle) +{ + int version = nsslowkey_version(handle); + + handle->version = version; + if (version != NSSLOWKEY_DB_FILE_VERSION) { + if (handle->db) { + keydb_Close(handle); + handle->db = NULL; + } + } + return handle->db != NULL; +} + +static NSSLOWKEYDBHandle * +nsslowkey_NewHandle(DB *dbHandle) +{ + NSSLOWKEYDBHandle *handle; + handle = (NSSLOWKEYDBHandle *)PORT_ZAlloc(sizeof(NSSLOWKEYDBHandle)); + if (handle == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + handle->appname = NULL; + handle->dbname = NULL; + handle->global_salt = NULL; + handle->updatedb = NULL; + handle->db = dbHandle; + handle->ref = 1; + handle->lock = PZ_NewLock(nssILockKeyDB); + + return handle; +} + +NSSLOWKEYDBHandle * +nsslowkey_OpenKeyDB(PRBool readOnly, const char *appName, const char *prefix, + NSSLOWKEYDBNameFunc namecb, void *cbarg) +{ + NSSLOWKEYDBHandle *handle = NULL; + SECStatus rv; + int openflags; + char *dbname = NULL; + + handle = nsslowkey_NewHandle(NULL); + + openflags = readOnly ? NO_RDONLY : NO_RDWR; + + dbname = (*namecb)(cbarg, NSSLOWKEY_DB_FILE_VERSION); + if (dbname == NULL) { + goto loser; + } + handle->appname = appName ? PORT_Strdup(appName) : NULL; + handle->dbname = (appName == NULL) ? PORT_Strdup(dbname) : (prefix ? PORT_Strdup(prefix) : NULL); + handle->readOnly = readOnly; + + handle->db = openOldDB(appName, prefix, dbname, openflags); + if (handle->db) { + verifyVersion(handle); + if (handle->version == 255) { + goto loser; + } + } + + /* if first open fails, try to create a new DB */ + if (handle->db == NULL) { + if (readOnly) { + goto loser; + } + + rv = openNewDB(appName, prefix, dbname, handle, namecb, cbarg); + /* two processes started to initialize the database at the same time. + * The multiprocess code blocked the second one, then had it retry to + * see if it can just open the database normally */ + if (rv == SECWouldBlock) { + handle->db = openOldDB(appName, prefix, dbname, openflags); + verifyVersion(handle); + if (handle->db == NULL) { + goto loser; + } + } else if (rv != SECSuccess) { + goto loser; + } + } + + handle->global_salt = GetKeyDBGlobalSalt(handle); + if (dbname) + PORT_Free(dbname); + return handle; + +loser: + + if (dbname) + PORT_Free(dbname); + PORT_SetError(SEC_ERROR_BAD_DATABASE); + nsslowkey_CloseKeyDB(handle); + return NULL; +} + +/* + * Close the database + */ +void +nsslowkey_CloseKeyDB(NSSLOWKEYDBHandle *handle) +{ + if (handle != NULL) { + if (handle->db != NULL) { + keydb_Close(handle); + } + if (handle->updatedb) { + handle->updatedb->close(handle->updatedb); + } + if (handle->dbname) + PORT_Free(handle->dbname); + if (handle->appname) + PORT_Free(handle->appname); + if (handle->global_salt) { + SECITEM_FreeItem(handle->global_salt, PR_TRUE); + } + if (handle->lock != NULL) { + SKIP_AFTER_FORK(PZ_DestroyLock(handle->lock)); + } + + PORT_Free(handle); + } +} + +/* Get the key database version */ +int +nsslowkey_GetKeyDBVersion(NSSLOWKEYDBHandle *handle) +{ + PORT_Assert(handle != NULL); + + return handle->version; +} + +/* + * Delete a private key that was stored in the database + */ +SECStatus +nsslowkey_DeleteKey(NSSLOWKEYDBHandle *handle, const SECItem *pubkey) +{ + DBT namekey; + int ret; + + if (handle == NULL) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + return (SECFailure); + } + + /* set up db key and data */ + namekey.data = pubkey->data; + namekey.size = pubkey->len; + + /* delete it from the database */ + ret = keydb_Del(handle, &namekey, 0); + if (ret) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + return (SECFailure); + } + + /* sync the database */ + ret = keydb_Sync(handle, 0); + if (ret) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + return (SECFailure); + } + + return (SECSuccess); +} + +/* + * Store a key in the database, indexed by its public key modulus.(value!) + */ +SECStatus +nsslowkey_StoreKeyByPublicKey(NSSLOWKEYDBHandle *handle, + NSSLOWKEYPrivateKey *privkey, + SECItem *pubKeyData, + char *nickname, + SDB *sdb) +{ + return nsslowkey_StoreKeyByPublicKeyAlg(handle, privkey, pubKeyData, + nickname, sdb, PR_FALSE); +} + +SECStatus +nsslowkey_UpdateNickname(NSSLOWKEYDBHandle *handle, + NSSLOWKEYPrivateKey *privkey, + SECItem *pubKeyData, + char *nickname, + SDB *sdb) +{ + return nsslowkey_StoreKeyByPublicKeyAlg(handle, privkey, pubKeyData, + nickname, sdb, PR_TRUE); +} + +/* see if the symetric CKA_ID already Exists. + */ +PRBool +nsslowkey_KeyForIDExists(NSSLOWKEYDBHandle *handle, SECItem *id) +{ + DBT namekey; + DBT dummy; + int status; + + namekey.data = (char *)id->data; + namekey.size = id->len; + status = keydb_Get(handle, &namekey, &dummy, 0); + if (status) { + return PR_FALSE; + } + + return PR_TRUE; +} + +/* see if the public key for this cert is in the database filed + * by modulus + */ +PRBool +nsslowkey_KeyForCertExists(NSSLOWKEYDBHandle *handle, NSSLOWCERTCertificate *cert) +{ + NSSLOWKEYPublicKey *pubkey = NULL; + DBT namekey; + DBT dummy; + int status; + + /* get cert's public key */ + pubkey = nsslowcert_ExtractPublicKey(cert); + if (pubkey == NULL) { + return PR_FALSE; + } + + /* TNH - make key from NSSLOWKEYPublicKey */ + switch (pubkey->keyType) { + case NSSLOWKEYRSAKey: + namekey.data = pubkey->u.rsa.modulus.data; + namekey.size = pubkey->u.rsa.modulus.len; + break; + case NSSLOWKEYDSAKey: + namekey.data = pubkey->u.dsa.publicValue.data; + namekey.size = pubkey->u.dsa.publicValue.len; + break; + case NSSLOWKEYDHKey: + namekey.data = pubkey->u.dh.publicValue.data; + namekey.size = pubkey->u.dh.publicValue.len; + break; + case NSSLOWKEYECKey: + namekey.data = pubkey->u.ec.publicValue.data; + namekey.size = pubkey->u.ec.publicValue.len; + break; + default: + /* XXX We don't do Fortezza or DH yet. */ + return PR_FALSE; + } + + if (handle->version != 3) { + unsigned char buf[SHA1_LENGTH]; + SHA1_HashBuf(buf, namekey.data, namekey.size); + /* NOTE: don't use pubkey after this! it's now thrashed */ + PORT_Memcpy(namekey.data, buf, sizeof(buf)); + namekey.size = sizeof(buf); + } + + status = keydb_Get(handle, &namekey, &dummy, 0); + /* some databases have the key stored as a signed value */ + if (status) { + unsigned char *buf = (unsigned char *)PORT_Alloc(namekey.size + 1); + if (buf) { + PORT_Memcpy(&buf[1], namekey.data, namekey.size); + buf[0] = 0; + namekey.data = buf; + namekey.size++; + status = keydb_Get(handle, &namekey, &dummy, 0); + PORT_Free(buf); + } + } + lg_nsslowkey_DestroyPublicKey(pubkey); + if (status) { + return PR_FALSE; + } + + return PR_TRUE; +} + +typedef struct NSSLowPasswordDataParamStr { + SECItem salt; + SECItem iter; +} NSSLowPasswordDataParam; + +static const SEC_ASN1Template NSSLOWPasswordParamTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(NSSLowPasswordDataParam) }, + { SEC_ASN1_OCTET_STRING, offsetof(NSSLowPasswordDataParam, salt) }, + { SEC_ASN1_INTEGER, offsetof(NSSLowPasswordDataParam, iter) }, + { 0 } +}; +struct LGEncryptedDataInfoStr { + SECAlgorithmID algorithm; + SECItem encryptedData; +}; +typedef struct LGEncryptedDataInfoStr LGEncryptedDataInfo; + +const SEC_ASN1Template lg_EncryptedDataInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(LGEncryptedDataInfo) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, + offsetof(LGEncryptedDataInfo, algorithm), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_OCTET_STRING, + offsetof(LGEncryptedDataInfo, encryptedData) }, + { 0 } +}; + +static SECItem * +nsslowkey_EncodePW(SECOidTag alg, const SECItem *salt, SECItem *data) +{ + NSSLowPasswordDataParam param; + LGEncryptedDataInfo edi; + PLArenaPool *arena; + unsigned char one = 1; + SECItem *epw = NULL; + SECItem *encParam; + int iterLen = 0; + int saltLen; + SECStatus rv; + + param.salt = *salt; + param.iter.type = siBuffer; /* encode as signed integer */ + param.iter.data = &one; + param.iter.len = 1; + edi.encryptedData = *data; + + iterLen = salt->len > 1 ? salt->data[salt->len - 1] : 2; + saltLen = (salt->len - iterLen) - 1; + /* if the resulting saltLen is a sha hash length, then assume that + * the iteration count is tacked on the end of the buffer */ + if ((saltLen == SHA1_LENGTH) || (saltLen == SHA256_LENGTH) || (saltLen == SHA384_LENGTH) || (saltLen == SHA224_LENGTH) || + (saltLen == SHA512_LENGTH)) { + param.iter.data = &salt->data[saltLen]; + param.iter.len = iterLen; + param.salt.len = saltLen; + } + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + return NULL; + } + + encParam = SEC_ASN1EncodeItem(arena, NULL, ¶m, + NSSLOWPasswordParamTemplate); + if (encParam == NULL) { + goto loser; + } + rv = SECOID_SetAlgorithmID(arena, &edi.algorithm, alg, encParam); + if (rv != SECSuccess) { + goto loser; + } + epw = SEC_ASN1EncodeItem(NULL, NULL, &edi, lg_EncryptedDataInfoTemplate); + +loser: + PORT_FreeArena(arena, PR_FALSE); + return epw; +} + +static SECItem * +nsslowkey_DecodePW(const SECItem *derData, SECOidTag *alg, SECItem *salt) +{ + NSSLowPasswordDataParam param; + LGEncryptedDataInfo edi; + PLArenaPool *arena; + SECItem *pwe = NULL; + SECStatus rv; + + salt->data = NULL; + param.iter.type = siBuffer; /* decode as signed integer */ + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + return NULL; + } + + rv = SEC_QuickDERDecodeItem(arena, &edi, lg_EncryptedDataInfoTemplate, + derData); + if (rv != SECSuccess) { + goto loser; + } + *alg = SECOID_GetAlgorithmTag(&edi.algorithm); + rv = SEC_QuickDERDecodeItem(arena, ¶m, NSSLOWPasswordParamTemplate, + &edi.algorithm.parameters); + if (rv != SECSuccess) { + goto loser; + } + /* if the iteration count isn't one, tack it at the end of the salt */ + if (!((param.iter.len == 1) && (param.iter.data[0] == 1))) { + int total_len = param.salt.len + param.iter.len + 1; + salt->data = PORT_Alloc(total_len); + if (salt->data == NULL) { + goto loser; + } + PORT_Memcpy(salt->data, param.salt.data, param.salt.len); + PORT_Memcpy(&salt->data[param.salt.len], param.iter.data, + param.iter.len); + salt->data[total_len - 1] = param.iter.len; + salt->len = total_len; + } else { + rv = SECITEM_CopyItem(NULL, salt, ¶m.salt); + if (rv != SECSuccess) { + goto loser; + } + } + pwe = SECITEM_DupItem(&edi.encryptedData); + +loser: + if (!pwe && salt->data) { + PORT_Free(salt->data); + salt->data = NULL; + } + PORT_FreeArena(arena, PR_FALSE); + return pwe; +} + +/* + * check to see if the user has a password + */ +static SECStatus +nsslowkey_GetPWCheckEntry(NSSLOWKEYDBHandle *handle, NSSLOWKEYPasswordEntry *entry) +{ + DBT checkkey; /*, checkdata; */ + NSSLOWKEYDBKey *dbkey = NULL; + SECItem *global_salt = NULL; + SECItem *item = NULL; + SECItem entryData, oid; + SECItem none = { siBuffer, NULL, 0 }; + SECStatus rv = SECFailure; + SECOidTag algorithm; + + if (handle == NULL) { + /* PORT_SetError */ + return (SECFailure); + } + + global_salt = GetKeyDBGlobalSalt(handle); + if (!global_salt) { + global_salt = &none; + } + if (global_salt->len > sizeof(entry->data)) { + /* PORT_SetError */ + goto loser; + } + + PORT_Memcpy(entry->data, global_salt->data, global_salt->len); + entry->salt.data = entry->data; + entry->salt.len = global_salt->len; + entry->value.data = &entry->data[entry->salt.len]; + + checkkey.data = KEYDB_PW_CHECK_STRING; + checkkey.size = KEYDB_PW_CHECK_LEN; + dbkey = get_dbkey(handle, &checkkey); + if (dbkey == NULL) { + /* handle 'FAKE' check here */ + goto loser; + } + + oid.len = dbkey->derPK.data[0]; + oid.data = &dbkey->derPK.data[1]; + + if (dbkey->derPK.len < (KEYDB_PW_CHECK_LEN + 1 + oid.len)) { + goto loser; + } + algorithm = SECOID_FindOIDTag(&oid); + entryData.type = siBuffer; + entryData.len = dbkey->derPK.len - (oid.len + 1); + entryData.data = &dbkey->derPK.data[oid.len + 1]; + + item = nsslowkey_EncodePW(algorithm, &dbkey->salt, &entryData); + if (!item || (item->len + entry->salt.len) > sizeof(entry->data)) { + goto loser; + } + PORT_Memcpy(entry->value.data, item->data, item->len); + entry->value.len = item->len; + rv = SECSuccess; + +loser: + if (item) { + SECITEM_FreeItem(item, PR_TRUE); + } + if (dbkey) { + sec_destroy_dbkey(dbkey); + } + if (global_salt != &none) { + SECITEM_FreeItem(global_salt, PR_TRUE); + } + return rv; +} + +/* + * check to see if the user has a password + */ +static SECStatus +nsslowkey_PutPWCheckEntry(NSSLOWKEYDBHandle *handle, NSSLOWKEYPasswordEntry *entry) +{ + DBT checkkey; + NSSLOWKEYDBKey *dbkey = NULL; + SECItem *item = NULL; + SECItem salt; + SECOidTag algid = SEC_OID_UNKNOWN; + SECStatus rv = SECFailure; + PLArenaPool *arena; + int ret; + + if (handle == NULL) { + /* PORT_SetError */ + return (SECFailure); + } + + checkkey.data = KEYDB_PW_CHECK_STRING; + checkkey.size = KEYDB_PW_CHECK_LEN; + + salt.data = NULL; + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + return SECFailure; + } + + item = nsslowkey_DecodePW(&entry->value, &algid, &salt); + if (item == NULL) { + goto loser; + } + + dbkey = PORT_ArenaZNew(arena, NSSLOWKEYDBKey); + if (dbkey == NULL) { + goto loser; + } + + dbkey->arena = arena; + + rv = SECITEM_CopyItem(arena, &dbkey->salt, &salt); + if (rv != SECSuccess) { + goto loser; + } + + rv = encodePWCheckEntry(arena, &dbkey->derPK, algid, item); + if (rv != SECSuccess) { + goto loser; + } + + rv = put_dbkey(handle, &checkkey, dbkey, PR_TRUE); + if (rv != SECSuccess) { + goto loser; + } + + if (handle->global_salt) { + SECITEM_FreeItem(handle->global_salt, PR_TRUE); + handle->global_salt = NULL; + } + rv = StoreKeyDBGlobalSalt(handle, &entry->salt); + if (rv != SECSuccess) { + goto loser; + } + ret = keydb_Sync(handle, 0); + if (ret) { + rv = SECFailure; + goto loser; + } + handle->global_salt = GetKeyDBGlobalSalt(handle); + +loser: + if (item) { + SECITEM_FreeItem(item, PR_TRUE); + } + if (arena) { + PORT_FreeArena(arena, PR_TRUE); + } + if (salt.data) { + PORT_Free(salt.data); + } + return rv; +} + +#ifdef EC_DEBUG +#define SEC_PRINT(str1, str2, num, sitem) \ + printf("pkcs11c.c:%s:%s (keytype=%d) [len=%d]\n", \ + str1, str2, num, sitem->len); \ + for (i = 0; i < sitem->len; i++) { \ + printf("%02x:", sitem->data[i]); \ + } \ + printf("\n") +#else +#define SEC_PRINT(a, b, c, d) +#endif /* EC_DEBUG */ + +SECStatus +seckey_encrypt_private_key(PLArenaPool *permarena, NSSLOWKEYPrivateKey *pk, + SDB *sdbpw, SECItem *result) +{ + NSSLOWKEYPrivateKeyInfo *pki = NULL; + SECStatus rv = SECFailure; + PLArenaPool *temparena = NULL; + SECItem *der_item = NULL; + SECItem *cipherText = NULL; + SECItem *dummy = NULL; +#ifdef EC_DEBUG + SECItem *fordebug = NULL; +#endif + int savelen; + + temparena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if (temparena == NULL) + goto loser; + + /* allocate structures */ + pki = (NSSLOWKEYPrivateKeyInfo *)PORT_ArenaZAlloc(temparena, + sizeof(NSSLOWKEYPrivateKeyInfo)); + der_item = (SECItem *)PORT_ArenaZAlloc(temparena, sizeof(SECItem)); + if ((pki == NULL) || (der_item == NULL)) + goto loser; + + /* setup private key info */ + dummy = SEC_ASN1EncodeInteger(temparena, &(pki->version), + NSSLOWKEY_PRIVATE_KEY_INFO_VERSION); + if (dummy == NULL) + goto loser; + + /* Encode the key, and set the algorithm (with params) */ + switch (pk->keyType) { + case NSSLOWKEYRSAKey: + lg_prepare_low_rsa_priv_key_for_asn1(pk); + dummy = SEC_ASN1EncodeItem(temparena, &(pki->privateKey), pk, + lg_nsslowkey_RSAPrivateKeyTemplate); + if (dummy == NULL) { + rv = SECFailure; + goto loser; + } + + rv = SECOID_SetAlgorithmID(temparena, &(pki->algorithm), + SEC_OID_PKCS1_RSA_ENCRYPTION, 0); + if (rv == SECFailure) { + goto loser; + } + + break; + case NSSLOWKEYDSAKey: + lg_prepare_low_dsa_priv_key_for_asn1(pk); + dummy = SEC_ASN1EncodeItem(temparena, &(pki->privateKey), pk, + lg_nsslowkey_DSAPrivateKeyTemplate); + if (dummy == NULL) { + rv = SECFailure; + goto loser; + } + + lg_prepare_low_pqg_params_for_asn1(&pk->u.dsa.params); + dummy = SEC_ASN1EncodeItem(temparena, NULL, &pk->u.dsa.params, + lg_nsslowkey_PQGParamsTemplate); + if (dummy == NULL) { + rv = SECFailure; + goto loser; + } + + rv = SECOID_SetAlgorithmID(temparena, &(pki->algorithm), + SEC_OID_ANSIX9_DSA_SIGNATURE, dummy); + if (rv == SECFailure) { + goto loser; + } + + break; + case NSSLOWKEYDHKey: + lg_prepare_low_dh_priv_key_for_asn1(pk); + dummy = SEC_ASN1EncodeItem(temparena, &(pki->privateKey), pk, + lg_nsslowkey_DHPrivateKeyTemplate); + if (dummy == NULL) { + rv = SECFailure; + goto loser; + } + + rv = SECOID_SetAlgorithmID(temparena, &(pki->algorithm), + SEC_OID_X942_DIFFIE_HELMAN_KEY, dummy); + if (rv == SECFailure) { + goto loser; + } + break; + case NSSLOWKEYECKey: + lg_prepare_low_ec_priv_key_for_asn1(pk); + /* Public value is encoded as a bit string so adjust length + * to be in bits before ASN encoding and readjust + * immediately after. + * + * Since the SECG specification recommends not including the + * parameters as part of ECPrivateKey, we zero out the curveOID + * length before encoding and restore it later. + */ + pk->u.ec.publicValue.len <<= 3; + savelen = pk->u.ec.ecParams.curveOID.len; + pk->u.ec.ecParams.curveOID.len = 0; + dummy = SEC_ASN1EncodeItem(temparena, &(pki->privateKey), pk, + lg_nsslowkey_ECPrivateKeyTemplate); + pk->u.ec.ecParams.curveOID.len = savelen; + pk->u.ec.publicValue.len >>= 3; + + if (dummy == NULL) { + rv = SECFailure; + goto loser; + } + + dummy = &pk->u.ec.ecParams.DEREncoding; + + /* At this point dummy should contain the encoded params */ + rv = SECOID_SetAlgorithmID(temparena, &(pki->algorithm), + SEC_OID_ANSIX962_EC_PUBLIC_KEY, dummy); + + if (rv == SECFailure) { + goto loser; + } + +#ifdef EC_DEBUG + fordebug = &(pki->privateKey); + SEC_PRINT("seckey_encrypt_private_key()", "PrivateKey", + pk->keyType, fordebug); +#endif + + break; + default: + /* We don't support DH or Fortezza private keys yet */ + PORT_Assert(PR_FALSE); + break; + } + + /* setup encrypted private key info */ + dummy = SEC_ASN1EncodeItem(temparena, der_item, pki, + lg_nsslowkey_PrivateKeyInfoTemplate); + + SEC_PRINT("seckey_encrypt_private_key()", "PrivateKeyInfo", + pk->keyType, der_item); + + if (dummy == NULL) { + rv = SECFailure; + goto loser; + } + + rv = lg_util_encrypt(temparena, sdbpw, dummy, &cipherText); + if (rv != SECSuccess) { + goto loser; + } + + rv = SECITEM_CopyItem(permarena, result, cipherText); + +loser: + + if (temparena != NULL) + PORT_FreeArena(temparena, PR_TRUE); + + return rv; +} + +static SECStatus +seckey_put_private_key(NSSLOWKEYDBHandle *keydb, DBT *index, SDB *sdbpw, + NSSLOWKEYPrivateKey *pk, char *nickname, PRBool update) +{ + NSSLOWKEYDBKey *dbkey = NULL; + PLArenaPool *arena = NULL; + SECStatus rv = SECFailure; + + if ((keydb == NULL) || (index == NULL) || (sdbpw == NULL) || + (pk == NULL)) + return SECFailure; + + arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if (arena == NULL) + return SECFailure; + + dbkey = (NSSLOWKEYDBKey *)PORT_ArenaZAlloc(arena, sizeof(NSSLOWKEYDBKey)); + if (dbkey == NULL) + goto loser; + dbkey->arena = arena; + dbkey->nickname = nickname; + + rv = seckey_encrypt_private_key(arena, pk, sdbpw, &dbkey->derPK); + if (rv != SECSuccess) + goto loser; + + rv = put_dbkey(keydb, index, dbkey, update); + +/* let success fall through */ +loser: + if (arena != NULL) + PORT_FreeArena(arena, PR_TRUE); + + return rv; +} + +/* + * Store a key in the database, indexed by its public key modulus. + * Note that the nickname is optional. It was only used by keyutil. + */ +SECStatus +nsslowkey_StoreKeyByPublicKeyAlg(NSSLOWKEYDBHandle *handle, + NSSLOWKEYPrivateKey *privkey, + SECItem *pubKeyData, + char *nickname, + SDB *sdbpw, + PRBool update) +{ + DBT namekey; + SECStatus rv; + + if (handle == NULL) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + return (SECFailure); + } + + /* set up db key and data */ + namekey.data = pubKeyData->data; + namekey.size = pubKeyData->len; + + /* encrypt the private key */ + rv = seckey_put_private_key(handle, &namekey, sdbpw, privkey, nickname, + update); + + return (rv); +} + +static NSSLOWKEYPrivateKey * +seckey_decrypt_private_key(SECItem *epki, + SDB *sdbpw) +{ + NSSLOWKEYPrivateKey *pk = NULL; + NSSLOWKEYPrivateKeyInfo *pki = NULL; + SECStatus rv = SECFailure; + PLArenaPool *temparena = NULL, *permarena = NULL; + SECItem *dest = NULL; +#ifdef EC_DEBUG + SECItem *fordebug = NULL; +#endif + + if ((epki == NULL) || (sdbpw == NULL)) + goto loser; + + temparena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + permarena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if ((temparena == NULL) || (permarena == NULL)) + goto loser; + + /* allocate temporary items */ + pki = (NSSLOWKEYPrivateKeyInfo *)PORT_ArenaZAlloc(temparena, + sizeof(NSSLOWKEYPrivateKeyInfo)); + + /* allocate permanent arena items */ + pk = (NSSLOWKEYPrivateKey *)PORT_ArenaZAlloc(permarena, + sizeof(NSSLOWKEYPrivateKey)); + + if ((pk == NULL) || (pki == NULL)) + goto loser; + + pk->arena = permarena; + + rv = lg_util_decrypt(sdbpw, epki, &dest); + if (rv != SECSuccess) { + goto loser; + } + + if (dest != NULL) { + SECItem newPrivateKey; + SECItem newAlgParms; + + SEC_PRINT("seckey_decrypt_private_key()", "PrivateKeyInfo", -1, + dest); + + rv = SEC_QuickDERDecodeItem(temparena, pki, + lg_nsslowkey_PrivateKeyInfoTemplate, dest); + if (rv == SECSuccess) { + switch (SECOID_GetAlgorithmTag(&pki->algorithm)) { + case SEC_OID_X500_RSA_ENCRYPTION: + case SEC_OID_PKCS1_RSA_ENCRYPTION: + pk->keyType = NSSLOWKEYRSAKey; + lg_prepare_low_rsa_priv_key_for_asn1(pk); + if (SECSuccess != SECITEM_CopyItem(permarena, &newPrivateKey, + &pki->privateKey)) + break; + rv = SEC_QuickDERDecodeItem(permarena, pk, + lg_nsslowkey_RSAPrivateKeyTemplate, + &newPrivateKey); + if (rv == SECSuccess) { + break; + } + /* Try decoding with the alternative template, but only allow + * a zero-length modulus for a secret key object. + * See bug 715073. + */ + rv = SEC_QuickDERDecodeItem(permarena, pk, + lg_nsslowkey_RSAPrivateKeyTemplate2, + &newPrivateKey); + /* A publicExponent of 0 is the defining property of a secret + * key disguised as an RSA key. When decoding with the + * alternative template, only accept a secret key with an + * improperly encoded modulus and a publicExponent of 0. + */ + if (rv == SECSuccess) { + if (pk->u.rsa.modulus.len == 2 && + pk->u.rsa.modulus.data[0] == SEC_ASN1_INTEGER && + pk->u.rsa.modulus.data[1] == 0 && + pk->u.rsa.publicExponent.len == 1 && + pk->u.rsa.publicExponent.data[0] == 0) { + /* Fix the zero-length integer by setting it to 0. */ + pk->u.rsa.modulus.data = pk->u.rsa.publicExponent.data; + pk->u.rsa.modulus.len = pk->u.rsa.publicExponent.len; + } else { + PORT_SetError(SEC_ERROR_BAD_DER); + rv = SECFailure; + } + } + break; + case SEC_OID_ANSIX9_DSA_SIGNATURE: + pk->keyType = NSSLOWKEYDSAKey; + lg_prepare_low_dsa_priv_key_for_asn1(pk); + if (SECSuccess != SECITEM_CopyItem(permarena, &newPrivateKey, + &pki->privateKey)) + break; + rv = SEC_QuickDERDecodeItem(permarena, pk, + lg_nsslowkey_DSAPrivateKeyTemplate, + &newPrivateKey); + if (rv != SECSuccess) + goto loser; + lg_prepare_low_pqg_params_for_asn1(&pk->u.dsa.params); + if (SECSuccess != SECITEM_CopyItem(permarena, &newAlgParms, + &pki->algorithm.parameters)) + break; + rv = SEC_QuickDERDecodeItem(permarena, &pk->u.dsa.params, + lg_nsslowkey_PQGParamsTemplate, + &newAlgParms); + break; + case SEC_OID_X942_DIFFIE_HELMAN_KEY: + pk->keyType = NSSLOWKEYDHKey; + lg_prepare_low_dh_priv_key_for_asn1(pk); + if (SECSuccess != SECITEM_CopyItem(permarena, &newPrivateKey, + &pki->privateKey)) + break; + rv = SEC_QuickDERDecodeItem(permarena, pk, + lg_nsslowkey_DHPrivateKeyTemplate, + &newPrivateKey); + break; + case SEC_OID_ANSIX962_EC_PUBLIC_KEY: + pk->keyType = NSSLOWKEYECKey; + lg_prepare_low_ec_priv_key_for_asn1(pk); + +#ifdef EC_DEBUG + fordebug = &pki->privateKey; + SEC_PRINT("seckey_decrypt_private_key()", "PrivateKey", + pk->keyType, fordebug); +#endif + if (SECSuccess != SECITEM_CopyItem(permarena, &newPrivateKey, + &pki->privateKey)) + break; + rv = SEC_QuickDERDecodeItem(permarena, pk, + lg_nsslowkey_ECPrivateKeyTemplate, + &newPrivateKey); + if (rv != SECSuccess) + goto loser; + + lg_prepare_low_ecparams_for_asn1(&pk->u.ec.ecParams); + + rv = SECITEM_CopyItem(permarena, + &pk->u.ec.ecParams.DEREncoding, + &pki->algorithm.parameters); + + if (rv != SECSuccess) + goto loser; + + /* Fill out the rest of EC params */ + rv = LGEC_FillParams(permarena, &pk->u.ec.ecParams.DEREncoding, + &pk->u.ec.ecParams); + + if (rv != SECSuccess) + goto loser; + + if (pk->u.ec.publicValue.len != 0) { + pk->u.ec.publicValue.len >>= 3; + } + + break; + default: + rv = SECFailure; + break; + } + } else if (PORT_GetError() == SEC_ERROR_BAD_DER) { + PORT_SetError(SEC_ERROR_BAD_PASSWORD); + goto loser; + } + } + +/* let success fall through */ +loser: + if (temparena != NULL) + PORT_FreeArena(temparena, PR_TRUE); + if (dest != NULL) + SECITEM_ZfreeItem(dest, PR_TRUE); + + if (rv != SECSuccess) { + if (permarena != NULL) + PORT_FreeArena(permarena, PR_TRUE); + pk = NULL; + } + + return pk; +} + +static NSSLOWKEYPrivateKey * +seckey_decode_encrypted_private_key(NSSLOWKEYDBKey *dbkey, SDB *sdbpw) +{ + if ((dbkey == NULL) || (sdbpw == NULL)) { + return NULL; + } + + return seckey_decrypt_private_key(&(dbkey->derPK), sdbpw); +} + +static NSSLOWKEYPrivateKey * +seckey_get_private_key(NSSLOWKEYDBHandle *keydb, DBT *index, char **nickname, + SDB *sdbpw) +{ + NSSLOWKEYDBKey *dbkey = NULL; + NSSLOWKEYPrivateKey *pk = NULL; + + if ((keydb == NULL) || (index == NULL) || (sdbpw == NULL)) { + return NULL; + } + + dbkey = get_dbkey(keydb, index); + if (dbkey == NULL) { + goto loser; + } + + if (nickname) { + if (dbkey->nickname && (dbkey->nickname[0] != 0)) { + *nickname = PORT_Strdup(dbkey->nickname); + } else { + *nickname = NULL; + } + } + + pk = seckey_decode_encrypted_private_key(dbkey, sdbpw); + +/* let success fall through */ +loser: + + if (dbkey != NULL) { + sec_destroy_dbkey(dbkey); + } + + return pk; +} + +/* + * Find a key in the database, indexed by its public key modulus + * This is used to find keys that have been stored before their + * certificate arrives. Once the certificate arrives the key + * is looked up by the public modulus in the certificate, and the + * re-stored by its nickname. + */ +NSSLOWKEYPrivateKey * +nsslowkey_FindKeyByPublicKey(NSSLOWKEYDBHandle *handle, SECItem *modulus, + SDB *sdbpw) +{ + DBT namekey; + NSSLOWKEYPrivateKey *pk = NULL; + + if (handle == NULL) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + return NULL; + } + + /* set up db key */ + namekey.data = modulus->data; + namekey.size = modulus->len; + + pk = seckey_get_private_key(handle, &namekey, NULL, sdbpw); + + /* no need to free dbkey, since its on the stack, and the data it + * points to is owned by the database + */ + return (pk); +} + +char * +nsslowkey_FindKeyNicknameByPublicKey(NSSLOWKEYDBHandle *handle, + SECItem *modulus, SDB *sdbpw) +{ + DBT namekey; + NSSLOWKEYPrivateKey *pk = NULL; + char *nickname = NULL; + + if (handle == NULL) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + return NULL; + } + + /* set up db key */ + namekey.data = modulus->data; + namekey.size = modulus->len; + + pk = seckey_get_private_key(handle, &namekey, &nickname, sdbpw); + if (pk) { + lg_nsslowkey_DestroyPrivateKey(pk); + } + + /* no need to free dbkey, since its on the stack, and the data it + * points to is owned by the database + */ + return (nickname); +} +/* ===== ENCODING ROUTINES ===== */ + +static SECStatus +encodePWCheckEntry(PLArenaPool *arena, SECItem *entry, SECOidTag alg, + SECItem *encCheck) +{ + SECOidData *oidData; + + oidData = SECOID_FindOIDByTag(alg); + if (oidData == NULL) { + return SECFailure; + } + + entry->len = 1 + oidData->oid.len + encCheck->len; + if (arena) { + entry->data = (unsigned char *)PORT_ArenaAlloc(arena, entry->len); + } else { + entry->data = (unsigned char *)PORT_Alloc(entry->len); + } + + if (entry->data == NULL) { + return SECFailure; + } + + /* first length of oid */ + entry->data[0] = (unsigned char)oidData->oid.len; + /* next oid itself */ + PORT_Memcpy(&entry->data[1], oidData->oid.data, oidData->oid.len); + /* finally the encrypted check string */ + PORT_Memcpy(&entry->data[1 + oidData->oid.len], encCheck->data, + encCheck->len); + + return SECSuccess; +} + +#define MAX_DB_SIZE 0xffff +/* + * Clear out all the keys in the existing database + */ +static SECStatus +nsslowkey_ResetKeyDB(NSSLOWKEYDBHandle *handle) +{ + SECStatus rv; + int errors = 0; + + if (handle->db == NULL) { + return (SECSuccess); + } + + if (handle->readOnly) { + /* set an error code */ + return SECFailure; + } + + if (handle->appname == NULL && handle->dbname == NULL) { + return SECFailure; + } + + keydb_Close(handle); + if (handle->appname) { + handle->db = + rdbopen(handle->appname, handle->dbname, "key", NO_CREATE, NULL); + } else { + handle->db = dbopen(handle->dbname, NO_CREATE, 0600, DB_HASH, 0); + } + if (handle->db == NULL) { + /* set an error code */ + return SECFailure; + } + + rv = makeGlobalVersion(handle); + if (rv != SECSuccess) { + errors++; + goto done; + } + + if (handle->global_salt) { + rv = StoreKeyDBGlobalSalt(handle, handle->global_salt); + } else { + rv = makeGlobalSalt(handle); + if (rv == SECSuccess) { + handle->global_salt = GetKeyDBGlobalSalt(handle); + } + } + if (rv != SECSuccess) { + errors++; + } + +done: + /* sync the database */ + (void)keydb_Sync(handle, 0); + db_InitComplete(handle->db); + + return (errors == 0 ? SECSuccess : SECFailure); +} + +static int +keydb_Get(NSSLOWKEYDBHandle *kdb, DBT *key, DBT *data, unsigned int flags) +{ + int ret; + PRLock *kdbLock = kdb->lock; + DB *db = kdb->db; + + PORT_Assert(kdbLock != NULL); + PZ_Lock(kdbLock); + + ret = (*db->get)(db, key, data, flags); + + (void)PZ_Unlock(kdbLock); + + return (ret); +} + +static int +keydb_Put(NSSLOWKEYDBHandle *kdb, DBT *key, DBT *data, unsigned int flags) +{ + int ret = 0; + PRLock *kdbLock = kdb->lock; + DB *db = kdb->db; + + PORT_Assert(kdbLock != NULL); + PZ_Lock(kdbLock); + + ret = (*db->put)(db, key, data, flags); + + (void)PZ_Unlock(kdbLock); + + return (ret); +} + +static int +keydb_Sync(NSSLOWKEYDBHandle *kdb, unsigned int flags) +{ + int ret; + PRLock *kdbLock = kdb->lock; + DB *db = kdb->db; + + PORT_Assert(kdbLock != NULL); + PZ_Lock(kdbLock); + + ret = (*db->sync)(db, flags); + + (void)PZ_Unlock(kdbLock); + + return (ret); +} + +static int +keydb_Del(NSSLOWKEYDBHandle *kdb, DBT *key, unsigned int flags) +{ + int ret; + PRLock *kdbLock = kdb->lock; + DB *db = kdb->db; + + PORT_Assert(kdbLock != NULL); + PZ_Lock(kdbLock); + + ret = (*db->del)(db, key, flags); + + (void)PZ_Unlock(kdbLock); + + return (ret); +} + +static int +keydb_Seq(NSSLOWKEYDBHandle *kdb, DBT *key, DBT *data, unsigned int flags) +{ + int ret; + PRLock *kdbLock = kdb->lock; + DB *db = kdb->db; + + PORT_Assert(kdbLock != NULL); + PZ_Lock(kdbLock); + + ret = (*db->seq)(db, key, data, flags); + + (void)PZ_Unlock(kdbLock); + + return (ret); +} + +static void +keydb_Close(NSSLOWKEYDBHandle *kdb) +{ + PRLock *kdbLock = kdb->lock; + DB *db = kdb->db; + + PORT_Assert(kdbLock != NULL); + SKIP_AFTER_FORK(PZ_Lock(kdbLock)); + + (*db->close)(db); + + SKIP_AFTER_FORK(PZ_Unlock(kdbLock)); + + return; +} + +/* + * SDB Entry Points for the Key DB + */ + +CK_RV +lg_GetMetaData(SDB *sdb, const char *id, SECItem *item1, SECItem *item2) +{ + NSSLOWKEYDBHandle *keydb; + NSSLOWKEYPasswordEntry entry; + SECStatus rv; + + keydb = lg_getKeyDB(sdb); + if (keydb == NULL) { + return CKR_TOKEN_WRITE_PROTECTED; + } + if (PORT_Strcmp(id, "password") != 0) { + /* shouldn't happen */ + return CKR_GENERAL_ERROR; /* no extra data stored */ + } + rv = nsslowkey_GetPWCheckEntry(keydb, &entry); + if (rv != SECSuccess) { + return CKR_GENERAL_ERROR; + } + item1->len = entry.salt.len; + PORT_Memcpy(item1->data, entry.salt.data, item1->len); + item2->len = entry.value.len; + PORT_Memcpy(item2->data, entry.value.data, item2->len); + return CKR_OK; +} + +CK_RV +lg_PutMetaData(SDB *sdb, const char *id, + const SECItem *item1, const SECItem *item2) +{ + NSSLOWKEYDBHandle *keydb; + NSSLOWKEYPasswordEntry entry; + SECStatus rv; + + keydb = lg_getKeyDB(sdb); + if (keydb == NULL) { + return CKR_TOKEN_WRITE_PROTECTED; + } + if (PORT_Strcmp(id, "password") != 0) { + /* shouldn't happen */ + return CKR_GENERAL_ERROR; /* no extra data stored */ + } + entry.salt = *item1; + entry.value = *item2; + rv = nsslowkey_PutPWCheckEntry(keydb, &entry); + if (rv != SECSuccess) { + return CKR_GENERAL_ERROR; + } + return CKR_OK; +} + +CK_RV +lg_DestroyMetaData(SDB *db, const char *id) +{ + return CKR_GENERAL_ERROR; /* no extra data stored */ +} + +CK_RV +lg_Reset(SDB *sdb) +{ + NSSLOWKEYDBHandle *keydb; + SECStatus rv; + + keydb = lg_getKeyDB(sdb); + if (keydb == NULL) { + return CKR_TOKEN_WRITE_PROTECTED; + } + rv = nsslowkey_ResetKeyDB(keydb); + if (rv != SECSuccess) { + return CKR_GENERAL_ERROR; + } + return CKR_OK; +} |