diff options
Diffstat (limited to 'security/nss/lib/softoken/keydb.c')
-rw-r--r-- | security/nss/lib/softoken/keydb.c | 2456 |
1 files changed, 2456 insertions, 0 deletions
diff --git a/security/nss/lib/softoken/keydb.c b/security/nss/lib/softoken/keydb.c new file mode 100644 index 000000000..21add55d1 --- /dev/null +++ b/security/nss/lib/softoken/keydb.c @@ -0,0 +1,2456 @@ +/* + * 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. + * + * Private Key Database code + * + * $Id$ + */ + +#include "lowkeyi.h" +#include "seccomon.h" +#include "sechash.h" +#include "secder.h" +#include "secasn1.h" +#include "secoid.h" +#include "blapi.h" +#include "secitem.h" +#include "pcert.h" +#include "mcom_db.h" +#include "lowpbe.h" +#include "secerr.h" +#include "cdbhdl.h" + +#include "keydbi.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 + +const SEC_ASN1Template nsslowkey_AttributeTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSLOWKEYAttribute) }, + { SEC_ASN1_OBJECT_ID, offsetof(NSSLOWKEYAttribute, attrType) }, + { SEC_ASN1_SET_OF, offsetof(NSSLOWKEYAttribute, attrValue), + SEC_AnyTemplate }, + { 0 } +}; + +const SEC_ASN1Template nsslowkey_SetOfAttributeTemplate[] = { + { SEC_ASN1_SET_OF, 0, nsslowkey_AttributeTemplate }, +}; +/* ASN1 Templates for new decoder/encoder */ +const SEC_ASN1Template nsslowkey_PrivateKeyInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSLOWKEYPrivateKeyInfo) }, + { SEC_ASN1_INTEGER, + offsetof(NSSLOWKEYPrivateKeyInfo,version) }, + { SEC_ASN1_INLINE, + offsetof(NSSLOWKEYPrivateKeyInfo,algorithm), + SECOID_AlgorithmIDTemplate }, + { SEC_ASN1_OCTET_STRING, + offsetof(NSSLOWKEYPrivateKeyInfo,privateKey) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(NSSLOWKEYPrivateKeyInfo, attributes), + nsslowkey_SetOfAttributeTemplate }, + { 0 } +}; + +const SEC_ASN1Template nsslowkey_PointerToPrivateKeyInfoTemplate[] = { + { SEC_ASN1_POINTER, 0, nsslowkey_PrivateKeyInfoTemplate } +}; + +const SEC_ASN1Template nsslowkey_EncryptedPrivateKeyInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSLOWKEYEncryptedPrivateKeyInfo) }, + { SEC_ASN1_INLINE, + offsetof(NSSLOWKEYEncryptedPrivateKeyInfo,algorithm), + 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 SECOidTag defaultKeyDBAlg = SEC_OID_PKCS12_PBE_WITH_SHA1_AND_TRIPLE_DES_CBC; + +/* + * Default algorithm for encrypting data in the key database + */ +SECOidTag +nsslowkey_GetDefaultKeyDBAlg(void) +{ + return(defaultKeyDBAlg); +} + +void +nsslowkey_SetDefaultKeyDBAlg(SECOidTag alg) +{ + defaultKeyDBAlg = alg; + + return; +} + +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; +} + +/* + * 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 */ + 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 = (* handle->db->get)(handle->db, 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 = (* handle->db->put)(handle->db, index, keydata, 0); + } else { + status = (* handle->db->put)(handle->db, index, keydata, + R_NOOVERWRITE); + } + + if ( status ) { + goto loser; + } + + /* sync the database */ + status = (* handle->db->sync)(handle->db, 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 = (* handle->db->seq)(handle->db, &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 ( (* handle->db->seq)(handle->db, &key, &data, R_NEXT) == 0 ); + + return(SECSuccess); +} + +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); +} + +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 = (* handle->db->get)(handle->db, &saltKey, &saltData, 0); + if ( ret ) { + return(NULL); + } + + return(decodeKeyDBGlobalSalt(&saltData)); +} + +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 = (* handle->db->put)(handle->db, &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 = (* handle->db->put)( handle->db, &saltKey, &saltData, 0); + if ( status ) { + return(SECFailure); + } + + return(SECSuccess); +} + +static char * +keyDBFilenameCallback(void *arg, int dbVersion) +{ + return(PORT_Strdup((char *)arg)); +} + +static SECStatus +ChangeKeyDBPasswordAlg(NSSLOWKEYDBHandle *handle, + SECItem *oldpwitem, SECItem *newpwitem, + SECOidTag new_algorithm); +/* + * Second pass of updating the key db. This time we have a password. + */ +static SECStatus +nsslowkey_UpdateKeyDBPass2(NSSLOWKEYDBHandle *handle, SECItem *pwitem) +{ + SECStatus rv; + + rv = ChangeKeyDBPasswordAlg(handle, pwitem, pwitem, + nsslowkey_GetDefaultKeyDBAlg()); + + return(rv); +} + +static SECStatus +encodePWCheckEntry(PLArenaPool *arena, SECItem *entry, SECOidTag alg, + SECItem *encCheck); + +static unsigned char +nsslowkey_version(DB *db) +{ + DBT versionKey; + DBT versionData; + int ret; + versionKey.data = VERSION_STRING; + versionKey.size = sizeof(VERSION_STRING)-1; + + /* lookup version string in database */ + ret = (* db->get)( db, &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(DB *db) +{ + DBT key; + DBT data; + int ret; + PRBool found = PR_FALSE; + + ret = (* db->seq)(db, &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 ( (* db->seq)(db, &key, &data, R_NEXT) == 0 ); + + return found; +} +/* + * 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; + SECItem *rc4key = NULL; + NSSLOWKEYDBKey *dbkey = NULL; + SECItem *oldSalt = NULL; + int ret; + SECItem checkitem; + + if ( handle->updatedb == NULL ) { + return(SECSuccess); + } + + /* + * check the version record + */ + version = nsslowkey_version(handle->updatedb); + if (version != 2) { + goto done; + } + + saltKey.data = SALT_STRING; + saltKey.size = sizeof(SALT_STRING) - 1; + + ret = (* handle->updatedb->get)(handle->updatedb, &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 = (* handle->updatedb->get)(handle->updatedb, &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(handle->updatedb)) { + 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 = (* handle->db->put)( handle->db, &saltKey, &saltData, 0); + if ( ret ) { + goto done; + } + ret = (* handle->db->put)( handle->db, &fcheckKey, &fcheckData, 0); + if ( ret ) { + goto done; + } + } else { + goto done; + } + } else { + /* put global salt into the new database now */ + ret = (* handle->db->put)( handle->db, &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 = (* handle->updatedb->seq)(handle->updatedb, &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 ( (* handle->updatedb->seq)(handle->updatedb, &key, &data, + R_NEXT) == 0 ); + + dbkey = NULL; + +done: + /* sync the database */ + ret = (* handle->db->sync)(handle->db, 0); + + (* handle->updatedb->close)(handle->updatedb); + handle->updatedb = NULL; + + if ( rc4key ) { + SECITEM_FreeItem(rc4key, PR_TRUE); + } + + 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; + char *updname = NULL; + DB *updatedb = NULL; + PRBool updated = PR_FALSE; + int ret; + + if (appName) { + handle->db = rdbopen( appName, prefix, "key", NO_CREATE); + } else { + handle->db = dbopen( dbname, NO_CREATE, 0600, DB_HASH, 0 ); + } + /* if create fails then we lose */ + if ( handle->db == NULL ) { + return SECFailure; + } + + rv = db_BeginTransaction(handle->db); + if (rv != SECSuccess) { + return rv; + } + + /* force a transactional read, which will verify that one and only one + * process attempts the update. */ + if (nsslowkey_version(handle->db) == NSSLOWKEY_DB_FILE_VERSION) { + /* someone else has already updated the database for us */ + db_FinishTransaction(handle->db, PR_FALSE); + return SECSuccess; + } + + /* + * if we are creating a multiaccess database, see if there is a + * local database we can update from. + */ + if (appName) { + updatedb = dbopen( dbname, NO_RDONLY, 0600, DB_HASH, 0 ); + if (updatedb) { + handle->version = nsslowkey_version(updatedb); + if (handle->version != NSSLOWKEY_DB_FILE_VERSION) { + (updatedb->close)(updatedb); + } else { + db_Copy(handle->db, updatedb); + (updatedb->close)(updatedb); + db_FinishTransaction(handle->db,PR_FALSE); + return SECSuccess; + } + } + } + + /* 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 = (* handle->db->sync)(handle->db, 0); + if ( ret ) { + rv = SECFailure; + goto loser; + } + rv = SECSuccess; + +loser: + db_FinishTransaction(handle->db, rv != SECSuccess); + return rv; +} + +NSSLOWKEYDBHandle * +nsslowkey_OpenKeyDB(PRBool readOnly, const char *appName, const char *prefix, + NSSLOWKEYDBNameFunc namecb, void *cbarg) +{ + NSSLOWKEYDBHandle *handle; + SECStatus rv; + int openflags; + char *dbname = NULL; + + handle = (NSSLOWKEYDBHandle *)PORT_ZAlloc (sizeof(NSSLOWKEYDBHandle)); + if (handle == NULL) { + PORT_SetError (SEC_ERROR_NO_MEMORY); + return NULL; + } + + openflags = readOnly ? NO_RDONLY : NO_RDWR; + + dbname = (*namecb)(cbarg, NSSLOWKEY_DB_FILE_VERSION); + if ( dbname == NULL ) { + goto loser; + } + + handle->dbname = PORT_Strdup(dbname); + handle->readOnly = readOnly; + + if (appName) { + handle->db = rdbopen( appName, prefix, "key", openflags); + } else { + handle->db = dbopen( dbname, openflags, 0600, DB_HASH, 0 ); + } + + /* check for correct version number */ + if (handle->db != NULL) { + handle->version = nsslowkey_version(handle->db); + if (handle->version == 255) { + goto loser; + } + if (handle->version != NSSLOWKEY_DB_FILE_VERSION ) { + /* bogus version number record, reset the database */ + (* handle->db->close)( handle->db ); + handle->db = NULL; + } + } + + /* 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); + 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); + + if ( handle->db ) { + (* handle->db->close)(handle->db); + } + if ( handle->updatedb ) { + (* handle->updatedb->close)(handle->updatedb); + } + PORT_Free(handle); + return NULL; +} + +/* + * Close the database + */ +void +nsslowkey_CloseKeyDB(NSSLOWKEYDBHandle *handle) +{ + if (handle != NULL) { + if (handle->db != NULL) { + (* handle->db->close)(handle->db); + } + if (handle->dbname) PORT_Free(handle->dbname); + if (handle->global_salt) { + SECITEM_FreeItem(handle->global_salt,PR_TRUE); + } + + 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, 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 = (* handle->db->del)(handle->db, &namekey, 0); + if ( ret ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + return(SECFailure); + } + + /* sync the database */ + ret = (* handle->db->sync)(handle->db, 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, + SECItem *arg) +{ + return nsslowkey_StoreKeyByPublicKeyAlg(handle, privkey, pubKeyData, + nickname, arg, nsslowkey_GetDefaultKeyDBAlg(),PR_FALSE); +} + +SECStatus +nsslowkey_UpdateNickname(NSSLOWKEYDBHandle *handle, + NSSLOWKEYPrivateKey *privkey, + SECItem *pubKeyData, + char *nickname, + SECItem *arg) +{ + return nsslowkey_StoreKeyByPublicKeyAlg(handle, privkey, pubKeyData, + nickname, arg, nsslowkey_GetDefaultKeyDBAlg(),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; + 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 = (* handle->db->get)(handle->db, &namekey, &dummy, 0); + nsslowkey_DestroyPublicKey(pubkey); + if ( status ) { + return PR_FALSE; + } + + return PR_TRUE; +} + +/* + * check to see if the user has a password + */ +SECStatus +nsslowkey_HasKeyDBPassword(NSSLOWKEYDBHandle *handle) +{ + DBT checkkey, checkdata; + int ret; + + if (handle == NULL) { + return(SECFailure); + } + + checkkey.data = KEYDB_PW_CHECK_STRING; + checkkey.size = KEYDB_PW_CHECK_LEN; + + ret = (* handle->db->get)(handle->db, &checkkey, &checkdata, 0 ); + if ( ret ) { + /* see if this was an updated DB first */ + checkkey.data = KEYDB_FAKE_PW_CHECK_STRING; + checkkey.size = KEYDB_FAKE_PW_CHECK_LEN; + ret = (* handle->db->get)(handle->db, &checkkey, &checkdata, 0 ); + if ( ret ) { + return(SECFailure); + } + } + + return(SECSuccess); +} + +/* + * Set up the password checker in the key database. + * This is done by encrypting a known plaintext with the user's key. + */ +SECStatus +nsslowkey_SetKeyDBPassword(NSSLOWKEYDBHandle *handle, SECItem *pwitem) +{ + return nsslowkey_SetKeyDBPasswordAlg(handle, pwitem, + nsslowkey_GetDefaultKeyDBAlg()); +} + +static SECStatus +HashPassword(unsigned char *hashresult, char *pw, SECItem *salt) +{ + SHA1Context *cx; + unsigned int outlen; + cx = SHA1_NewContext(); + if ( cx == NULL ) { + return(SECFailure); + } + + SHA1_Begin(cx); + if ( ( salt != NULL ) && ( salt->data != NULL ) ) { + SHA1_Update(cx, salt->data, salt->len); + } + + SHA1_Update(cx, (unsigned char *)pw, PORT_Strlen(pw)); + SHA1_End(cx, hashresult, &outlen, SHA1_LENGTH); + + SHA1_DestroyContext(cx, PR_TRUE); + + return(SECSuccess); +} + +SECItem * +nsslowkey_HashPassword(char *pw, SECItem *salt) +{ + SECItem *pwitem; + SECStatus rv; + + pwitem = (SECItem *)PORT_ZAlloc(sizeof(SECItem)); + if ( pwitem == NULL ) { + return(NULL); + } + pwitem->len = SHA1_LENGTH; + pwitem->data = (unsigned char *)PORT_ZAlloc(SHA1_LENGTH); + if ( pwitem->data == NULL ) { + PORT_Free(pwitem); + return(NULL); + } + if ( pw ) { + rv = HashPassword(pwitem->data, pw, salt); + if ( rv != SECSuccess ) { + SECITEM_ZfreeItem(pwitem, PR_TRUE); + return(NULL); + } + } + + return(pwitem); +} + +/* Derive the actual password value for the database from a pw string */ +SECItem * +nsslowkey_DeriveKeyDBPassword(NSSLOWKEYDBHandle *keydb, char *pw) +{ + PORT_Assert(keydb != NULL); + PORT_Assert(pw != NULL); + if (keydb == NULL || pw == NULL) return(NULL); + + return nsslowkey_HashPassword(pw, keydb->global_salt); +} + +#if 0 +/* Appears obsolete - TNH */ +/* get the algorithm with which a private key + * is encrypted. + */ +SECOidTag +seckey_get_private_key_algorithm(NSSLOWKEYDBHandle *keydb, DBT *index) +{ + NSSLOWKEYDBKey *dbkey = NULL; + SECOidTag algorithm = SEC_OID_UNKNOWN; + NSSLOWKEYEncryptedPrivateKeyInfo *epki = NULL; + PLArenaPool *poolp = NULL; + SECStatus rv; + + poolp = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if(poolp == NULL) + return (SECOidTag)SECFailure; /* TNH - this is bad */ + + dbkey = get_dbkey(keydb, index); + if(dbkey == NULL) + return (SECOidTag)SECFailure; + + epki = (NSSLOWKEYEncryptedPrivateKeyInfo *)PORT_ArenaZAlloc(poolp, + sizeof(NSSLOWKEYEncryptedPrivateKeyInfo)); + if(epki == NULL) + goto loser; + rv = SEC_ASN1DecodeItem(poolp, epki, + nsslowkey_EncryptedPrivateKeyInfoTemplate, &dbkey->derPK); + if(rv == SECFailure) + goto loser; + + algorithm = SECOID_GetAlgorithmTag(&epki->algorithm); + + /* let success fall through */ +loser: + if(poolp != NULL) + PORT_FreeArena(poolp, PR_TRUE);\ + if(dbkey != NULL) + sec_destroy_dbkey(dbkey); + + return algorithm; +} +#endif + +/* + * Derive an RC4 key from a password key and a salt. This + * was the method to used to encrypt keys in the version 2? + * database + */ +SECItem * +seckey_create_rc4_key(SECItem *pwitem, SECItem *salt) +{ + MD5Context *md5 = NULL; + unsigned int part; + SECStatus rv = SECFailure; + SECItem *key = NULL; + + key = (SECItem *)PORT_ZAlloc(sizeof(SECItem)); + if(key != NULL) + { + key->data = (unsigned char *)PORT_ZAlloc(sizeof(unsigned char) * + MD5_LENGTH); + key->len = MD5_LENGTH; + if(key->data != NULL) + { + md5 = MD5_NewContext(); + if ( md5 != NULL ) + { + MD5_Begin(md5); + MD5_Update(md5, salt->data, salt->len); + MD5_Update(md5, pwitem->data, pwitem->len); + MD5_End(md5, key->data, &part, MD5_LENGTH); + MD5_DestroyContext(md5, PR_TRUE); + rv = SECSuccess; + } + } + + if(rv != SECSuccess) + { + SECITEM_FreeItem(key, PR_TRUE); + key = NULL; + } + } + + return key; +} + +SECItem * +seckey_create_rc4_salt(void) +{ + SECItem *salt = NULL; + SECStatus rv = SECFailure; + + salt = (SECItem *)PORT_ZAlloc(sizeof(SECItem)); + if(salt == NULL) + return NULL; + + salt->data = (unsigned char *)PORT_ZAlloc(sizeof(unsigned char) * + SALT_LENGTH); + if(salt->data != NULL) + { + salt->len = SALT_LENGTH; + RNG_GenerateGlobalRandomBytes(salt->data, salt->len); + rv = SECSuccess; + } + + if(rv == SECFailure) + { + SECITEM_FreeItem(salt, PR_TRUE); + salt = NULL; + } + + return salt; +} + +SECItem * +seckey_rc4_decode(SECItem *key, SECItem *src) +{ + SECItem *dest = NULL; + RC4Context *ctxt = NULL; + SECStatus rv = SECFailure; + + if((key == NULL) || (src == NULL)) + return NULL; + + dest = (SECItem *)PORT_ZAlloc(sizeof(SECItem)); + if(dest == NULL) + return NULL; + + dest->data = (unsigned char *)PORT_ZAlloc(sizeof(unsigned char) * + (src->len + 64)); /* TNH - padding? */ + if(dest->data != NULL) + { + ctxt = RC4_CreateContext(key->data, key->len); + if(ctxt != NULL) + { + rv = RC4_Decrypt(ctxt, dest->data, &dest->len, + src->len + 64, src->data, src->len); + RC4_DestroyContext(ctxt, PR_TRUE); + } + } + + if(rv == SECFailure) + if(dest != NULL) + { + SECITEM_FreeItem(dest, PR_TRUE); + dest = NULL; + } + + return dest; +} + +/* TNH - keydb is unused */ +/* TNH - the pwitem should be the derived key for RC4 */ +NSSLOWKEYEncryptedPrivateKeyInfo * +seckey_encrypt_private_key( + NSSLOWKEYPrivateKey *pk, SECItem *pwitem, NSSLOWKEYDBHandle *keydb, + SECOidTag algorithm, SECItem **salt) +{ + NSSLOWKEYEncryptedPrivateKeyInfo *epki = NULL; + NSSLOWKEYPrivateKeyInfo *pki = NULL; + SECStatus rv = SECFailure; + PLArenaPool *temparena = NULL, *permarena = NULL; + SECItem *der_item = NULL; + NSSPKCS5PBEParameter *param = NULL; + SECItem *dummy = NULL, *dest = NULL; + SECAlgorithmID *algid; + + *salt = NULL; + permarena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if(permarena == NULL) + return NULL; + + temparena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if(temparena == NULL) + goto loser; + + /* allocate structures */ + epki = (NSSLOWKEYEncryptedPrivateKeyInfo *)PORT_ArenaZAlloc(permarena, + sizeof(NSSLOWKEYEncryptedPrivateKeyInfo)); + pki = (NSSLOWKEYPrivateKeyInfo *)PORT_ArenaZAlloc(temparena, + sizeof(NSSLOWKEYPrivateKeyInfo)); + der_item = (SECItem *)PORT_ArenaZAlloc(temparena, sizeof(SECItem)); + if((epki == NULL) || (pki == NULL) || (der_item == NULL)) + goto loser; + + epki->arena = permarena; + + /* 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: + prepare_low_rsa_priv_key_for_asn1(pk); + dummy = SEC_ASN1EncodeItem(temparena, &(pki->privateKey), pk, + 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: + prepare_low_dsa_priv_key_for_asn1(pk); + dummy = SEC_ASN1EncodeItem(temparena, &(pki->privateKey), pk, + nsslowkey_DSAPrivateKeyTemplate); + if (dummy == NULL) { + rv = SECFailure; + goto loser; + } + + prepare_low_pqg_params_for_asn1(&pk->u.dsa.params); + dummy = SEC_ASN1EncodeItem(temparena, NULL, &pk->u.dsa.params, + 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: + prepare_low_dh_priv_key_for_asn1(pk); + dummy = SEC_ASN1EncodeItem(temparena, &(pki->privateKey), pk, + 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; + 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, + nsslowkey_PrivateKeyInfoTemplate); + if(dummy == NULL) { + rv = SECFailure; + goto loser; + } + + rv = SECFailure; /* assume failure */ + *salt = seckey_create_rc4_salt(); + if (*salt == NULL) { + goto loser; + } + + param = nsspkcs5_NewParam(algorithm,*salt,1); + if (param == NULL) { + goto loser; + } + + dest = nsspkcs5_CipherData(param, pwitem, der_item, PR_TRUE, NULL); + if (dest == NULL) { + goto loser; + } + + rv = SECITEM_CopyItem(permarena, &epki->encryptedData, dest); + if (rv != SECSuccess) { + goto loser; + } + + algid = nsspkcs5_CreateAlgorithmID(permarena, algorithm, param); + if (algid == NULL) { + rv = SECFailure; + goto loser; + } + + rv = SECOID_CopyAlgorithmID(permarena, &epki->algorithm, algid); + SECOID_DestroyAlgorithmID(algid, PR_TRUE); + +loser: + if(dest != NULL) + SECITEM_FreeItem(dest, PR_TRUE); + + if(param != NULL) + nsspkcs5_DestroyPBEParameter(param); + + /* let success fall through */ + + if(rv == SECFailure) + { + PORT_FreeArena(permarena, PR_TRUE); + epki = NULL; + if(*salt != NULL) + SECITEM_FreeItem(*salt, PR_TRUE); + } + + if(temparena != NULL) + PORT_FreeArena(temparena, PR_TRUE); + + return epki; +} + +static SECStatus +seckey_put_private_key(NSSLOWKEYDBHandle *keydb, DBT *index, SECItem *pwitem, + NSSLOWKEYPrivateKey *pk, char *nickname, PRBool update, + SECOidTag algorithm) +{ + NSSLOWKEYDBKey *dbkey = NULL; + NSSLOWKEYEncryptedPrivateKeyInfo *epki = NULL; + PLArenaPool *temparena = NULL, *permarena = NULL; + SECItem *dummy = NULL; + SECItem *salt = NULL; + SECStatus rv = SECFailure; + + if((keydb == NULL) || (index == NULL) || (pwitem == NULL) || + (pk == NULL)) + return SECFailure; + + permarena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if(permarena == NULL) + return SECFailure; + + dbkey = (NSSLOWKEYDBKey *)PORT_ArenaZAlloc(permarena, sizeof(NSSLOWKEYDBKey)); + if(dbkey == NULL) + goto loser; + dbkey->arena = permarena; + dbkey->nickname = nickname; + + /* TNH - for RC4, the salt should be created here */ + + epki = seckey_encrypt_private_key(pk, pwitem, keydb, algorithm, &salt); + if(epki == NULL) + goto loser; + temparena = epki->arena; + + if(salt != NULL) + { + rv = SECITEM_CopyItem(permarena, &(dbkey->salt), salt); + SECITEM_ZfreeItem(salt, PR_TRUE); + } + + dummy = SEC_ASN1EncodeItem(permarena, &(dbkey->derPK), epki, + nsslowkey_EncryptedPrivateKeyInfoTemplate); + if(dummy == NULL) + rv = SECFailure; + else + rv = put_dbkey(keydb, index, dbkey, update); + + /* let success fall through */ +loser: + if(rv != SECSuccess) + if(permarena != NULL) + PORT_FreeArena(permarena, PR_TRUE); + if(temparena != NULL) + PORT_FreeArena(temparena, 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, + SECItem *pwitem, + SECOidTag algorithm, + 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, pwitem, privkey, nickname, + update, algorithm); + + return(rv); +} + +NSSLOWKEYPrivateKey * +seckey_decrypt_private_key(NSSLOWKEYEncryptedPrivateKeyInfo *epki, + SECItem *pwitem) +{ + NSSLOWKEYPrivateKey *pk = NULL; + NSSLOWKEYPrivateKeyInfo *pki = NULL; + SECStatus rv = SECFailure; + SECOidTag algorithm; + PLArenaPool *temparena = NULL, *permarena = NULL; + SECItem *salt = NULL, *dest = NULL, *key = NULL; + NSSPKCS5PBEParameter *param; + + if((epki == NULL) || (pwitem == 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; + + algorithm = SECOID_GetAlgorithmTag(&(epki->algorithm)); + switch(algorithm) + { + case SEC_OID_RC4: + salt = SECITEM_DupItem(&epki->algorithm.parameters); + if(salt != NULL) + { + key = seckey_create_rc4_key(pwitem, salt); + if(key != NULL) + { + dest = seckey_rc4_decode(key, &epki->encryptedData); + } + } + if(salt != NULL) + SECITEM_ZfreeItem(salt, PR_TRUE); + if(key != NULL) + SECITEM_ZfreeItem(key, PR_TRUE); + break; + default: + /* we depend on the fact that if this key was encoded with + * DES, that the pw was also encoded with DES, so we don't have + * to do the update here, the password code will handle it. */ + param = nsspkcs5_AlgidToParam(&epki->algorithm); + if (param == NULL) { + break; + } + dest = nsspkcs5_CipherData(param, pwitem, &epki->encryptedData, + PR_FALSE, NULL); + nsspkcs5_DestroyPBEParameter(param); + break; + } + + if(dest != NULL) + { + rv = SEC_ASN1DecodeItem(temparena, pki, + 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; + prepare_low_rsa_priv_key_for_asn1(pk); + rv = SEC_ASN1DecodeItem(permarena, pk, + nsslowkey_RSAPrivateKeyTemplate, + &pki->privateKey); + break; + case SEC_OID_ANSIX9_DSA_SIGNATURE: + pk->keyType = NSSLOWKEYDSAKey; + prepare_low_dsa_priv_key_for_asn1(pk); + rv = SEC_ASN1DecodeItem(permarena, pk, + nsslowkey_DSAPrivateKeyTemplate, + &pki->privateKey); + if (rv != SECSuccess) + goto loser; + prepare_low_pqg_params_for_asn1(&pk->u.dsa.params); + rv = SEC_ASN1DecodeItem(permarena, &pk->u.dsa.params, + nsslowkey_PQGParamsTemplate, + &pki->algorithm.parameters); + break; + case SEC_OID_X942_DIFFIE_HELMAN_KEY: + pk->keyType = NSSLOWKEYDHKey; + prepare_low_dh_priv_key_for_asn1(pk); + rv = SEC_ASN1DecodeItem(permarena, pk, + nsslowkey_DHPrivateKeyTemplate, + &pki->privateKey); + 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, SECItem *pwitem) +{ + NSSLOWKEYPrivateKey *pk = NULL; + NSSLOWKEYEncryptedPrivateKeyInfo *epki; + PLArenaPool *temparena = NULL; + SECStatus rv; + SECOidTag algorithm; + + if( ( dbkey == NULL ) || ( pwitem == NULL ) ) { + return NULL; + } + + temparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if(temparena == NULL) { + return NULL; + } + + epki = (NSSLOWKEYEncryptedPrivateKeyInfo *) + PORT_ArenaZAlloc(temparena, sizeof(NSSLOWKEYEncryptedPrivateKeyInfo)); + + if(epki == NULL) { + goto loser; + } + + rv = SEC_ASN1DecodeItem(temparena, epki, + nsslowkey_EncryptedPrivateKeyInfoTemplate, + &(dbkey->derPK)); + if(rv != SECSuccess) { + goto loser; + } + + algorithm = SECOID_GetAlgorithmTag(&(epki->algorithm)); + switch(algorithm) + { + case SEC_OID_RC4: + /* TNH - this code should derive the actual RC4 key from salt and + pwitem */ + rv = SECITEM_CopyItem(temparena, &(epki->algorithm.parameters), + &(dbkey->salt)); + break; + default: + break; + } + + pk = seckey_decrypt_private_key(epki, pwitem); + + /* let success fall through */ +loser: + + PORT_FreeArena(temparena, PR_TRUE); + return pk; +} + +NSSLOWKEYPrivateKey * +seckey_get_private_key(NSSLOWKEYDBHandle *keydb, DBT *index, char **nickname, + SECItem *pwitem) +{ + NSSLOWKEYDBKey *dbkey = NULL; + NSSLOWKEYPrivateKey *pk = NULL; + + if( ( keydb == NULL ) || ( index == NULL ) || ( pwitem == 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, pwitem); + + /* let success fall through */ +loser: + + if ( dbkey != NULL ) { + sec_destroy_dbkey(dbkey); + } + + return pk; +} + +/* + * used by pkcs11 to import keys into it's object format... In the future + * we really need a better way to tie in... + */ +NSSLOWKEYPrivateKey * +nsslowkey_DecryptKey(DBT *key, SECItem *pwitem, + NSSLOWKEYDBHandle *handle) { + return seckey_get_private_key(handle,key,NULL,pwitem); +} + +/* + * 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, + SECItem *pwitem) +{ + 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, pwitem); + + /* 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, SECItem *pwitem) +{ + 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, pwitem); + if (pk) { + 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; + SECStatus rv; + + oidData = SECOID_FindOIDByTag(alg); + if ( oidData == NULL ) { + rv = SECFailure; + goto loser; + } + + 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 ) { + goto loser; + } + + /* 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); + +loser: + return(SECFailure); +} + +/* + * Set up the password checker in the key database. + * This is done by encrypting a known plaintext with the user's key. + */ +SECStatus +nsslowkey_SetKeyDBPasswordAlg(NSSLOWKEYDBHandle *handle, + SECItem *pwitem, SECOidTag algorithm) +{ + DBT checkkey; + NSSPKCS5PBEParameter *param = NULL; + SECStatus rv = SECFailure; + NSSLOWKEYDBKey *dbkey = NULL; + PLArenaPool *arena; + SECItem *salt = NULL; + SECItem *dest = NULL, test_key; + + if (handle == NULL) { + return(SECFailure); + } + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + rv = SECFailure; + goto loser; + } + + dbkey = (NSSLOWKEYDBKey *)PORT_ArenaZAlloc(arena, sizeof(NSSLOWKEYDBKey)); + if ( dbkey == NULL ) { + rv = SECFailure; + goto loser; + } + + dbkey->arena = arena; + + /* encrypt key */ + checkkey.data = test_key.data = (unsigned char *)KEYDB_PW_CHECK_STRING; + checkkey.size = test_key.len = KEYDB_PW_CHECK_LEN; + + salt = seckey_create_rc4_salt(); + if(salt == NULL) { + rv = SECFailure; + goto loser; + } + + param = nsspkcs5_NewParam(algorithm, salt, 1); + if (param == NULL) { + rv = SECFailure; + goto loser; + } + + dest = nsspkcs5_CipherData(param, pwitem, &test_key, PR_TRUE, NULL); + if (dest == NULL) + { + rv = SECFailure; + goto loser; + } + + rv = SECITEM_CopyItem(arena, &dbkey->salt, salt); + if (rv == SECFailure) { + goto loser; + } + + rv = encodePWCheckEntry(arena, &dbkey->derPK, algorithm, dest); + + if ( rv != SECSuccess ) { + goto loser; + } + + rv = put_dbkey(handle, &checkkey, dbkey, PR_TRUE); + + /* let success fall through */ +loser: + if ( arena != NULL ) { + PORT_FreeArena(arena, PR_TRUE); + } + + if ( dest != NULL ) { + SECITEM_ZfreeItem(dest, PR_TRUE); + } + + if ( salt != NULL ) { + SECITEM_ZfreeItem(salt, PR_TRUE); + } + + if (param != NULL) { + nsspkcs5_DestroyPBEParameter(param); + } + + return(rv); +} + +static SECStatus +seckey_CheckKeyDB1Password(NSSLOWKEYDBHandle *handle, SECItem *pwitem) +{ + SECStatus rv = SECFailure; + keyList keylist; + keyNode *node = NULL; + NSSLOWKEYPrivateKey *privkey = NULL; + + + /* + * first find a key + */ + + /* traverse the database, collecting the keys of all records */ + keylist.arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( keylist.arena == NULL ) + { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return(SECFailure); + } + keylist.head = NULL; + + /* TNH - TraverseKeys should not be public, since it exposes + the underlying DBT data type. */ + rv = nsslowkey_TraverseKeys(handle, sec_add_key_to_list, (void *)&keylist); + if ( rv != SECSuccess ) + goto done; + + /* just get the first key from the list */ + node = keylist.head; + + /* no private keys, accept any password */ + if (node == NULL) { + rv = SECSuccess; + goto done; + } + privkey = seckey_get_private_key(handle, &node->key, NULL, pwitem); + if (privkey == NULL) { + rv = SECFailure; + goto done; + } + + /* if we can decrypt the private key, then we had the correct password */ + rv = SECSuccess; + nsslowkey_DestroyPrivateKey(privkey); + +done: + + /* free the arena */ + if ( keylist.arena ) { + PORT_FreeArena(keylist.arena, PR_FALSE); + } + + return(rv); +} + +/* + * check to see if the user has typed the right password + */ +SECStatus +nsslowkey_CheckKeyDBPassword(NSSLOWKEYDBHandle *handle, SECItem *pwitem) +{ + DBT checkkey; + DBT checkdata; + NSSPKCS5PBEParameter *param = NULL; + SECStatus rv = SECFailure; + NSSLOWKEYDBKey *dbkey = NULL; + SECItem *key = NULL; + SECItem *dest = NULL; + SECOidTag algorithm; + SECItem oid; + SECItem encstring; + PRBool update = PR_FALSE; + int ret; + + if (handle == NULL) { + goto loser; + } + + checkkey.data = KEYDB_PW_CHECK_STRING; + checkkey.size = KEYDB_PW_CHECK_LEN; + + dbkey = get_dbkey(handle, &checkkey); + + if ( dbkey == NULL ) { + checkkey.data = KEYDB_FAKE_PW_CHECK_STRING; + checkkey.size = KEYDB_FAKE_PW_CHECK_LEN; + ret = (* handle->db->get)(handle->db, &checkkey, + &checkdata, 0 ); + if (ret) { + goto loser; + } + /* if we have the fake PW_CHECK, then try to decode the key + * rather than the pwcheck item. + */ + rv = seckey_CheckKeyDB1Password(handle,pwitem); + if (rv == SECSuccess) { + /* OK we have enough to complete our conversion */ + nsslowkey_UpdateKeyDBPass2(handle,pwitem); + } + return rv; + } + + /* build the oid item */ + oid.len = dbkey->derPK.data[0]; + oid.data = &dbkey->derPK.data[1]; + + /* make sure entry is the correct length + * since we are probably using a block cipher, the block will be + * padded, so we may get a bit more than the exact size we need. + */ + if ( dbkey->derPK.len < (KEYDB_PW_CHECK_LEN + 1 + oid.len ) ) { + goto loser; + } + + /* find the algorithm tag */ + algorithm = SECOID_FindOIDTag(&oid); + + /* make a secitem of the encrypted check string */ + encstring.len = dbkey->derPK.len - ( oid.len + 1 ); + encstring.data = &dbkey->derPK.data[oid.len+1]; + encstring.type = 0; + + switch(algorithm) + { + case SEC_OID_RC4: + key = seckey_create_rc4_key(pwitem, &dbkey->salt); + if(key != NULL) { + dest = seckey_rc4_decode(key, &encstring); + SECITEM_FreeItem(key, PR_TRUE); + } + break; + default: + param = nsspkcs5_NewParam(algorithm, &dbkey->salt, 1); + if (param != NULL) { + /* Decrypt - this function implements a workaround for + * a previous coding error. It will decrypt values using + * DES rather than 3DES, if the initial try at 3DES + * decryption fails. In this case, the update flag is + * set to TRUE. This indication is used later to force + * an update of the database to "real" 3DES encryption. + */ + dest = nsspkcs5_CipherData(param, pwitem, + &encstring, PR_FALSE, &update); + nsspkcs5_DestroyPBEParameter(param); + } + break; + } + + if(dest == NULL) { + goto loser; + } + + if ((dest->len == KEYDB_PW_CHECK_LEN) && + (PORT_Memcmp(dest->data, + KEYDB_PW_CHECK_STRING, KEYDB_PW_CHECK_LEN) == 0)) { + rv = SECSuccess; + /* we succeeded */ + if ( algorithm == SEC_OID_RC4 ) { + /* partially updated database */ + nsslowkey_UpdateKeyDBPass2(handle, pwitem); + } + /* Force an update of the password to remove the incorrect DES + * encryption (see the note above) + */ + if (update && + (algorithm == SEC_OID_PKCS12_PBE_WITH_SHA1_AND_TRIPLE_DES_CBC)) { + /* data base was encoded with DES not triple des, fix it */ + nsslowkey_UpdateKeyDBPass2(handle,pwitem); + } + } + +loser: + sec_destroy_dbkey(dbkey); + if(dest != NULL) { + SECITEM_ZfreeItem(dest, PR_TRUE); + } + + return(rv); +} + +/* + * Change the database password and/or algorithm. This internal + * routine does not check the old password. That must be done by + * the caller. + */ +static SECStatus +ChangeKeyDBPasswordAlg(NSSLOWKEYDBHandle *handle, + SECItem *oldpwitem, SECItem *newpwitem, + SECOidTag new_algorithm) +{ + SECStatus rv; + keyList keylist; + keyNode *node = NULL; + NSSLOWKEYPrivateKey *privkey = NULL; + char *nickname; + DBT newkey; + int ret; + + /* traverse the database, collecting the keys of all records */ + keylist.arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( keylist.arena == NULL ) + { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return(SECFailure); + } + keylist.head = NULL; + + rv = db_BeginTransaction(handle->db); + if (rv != SECSuccess) { + goto loser; + } + + /* TNH - TraverseKeys should not be public, since it exposes + the underlying DBT data type. */ + rv = nsslowkey_TraverseKeys(handle, sec_add_key_to_list, (void *)&keylist); + if ( rv != SECSuccess ) + goto loser; + + /* walk the list, re-encrypting each entry */ + node = keylist.head; + while ( node != NULL ) + { + privkey = seckey_get_private_key(handle, &node->key, &nickname, + oldpwitem); + + if (privkey == NULL) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + rv = SECFailure; + goto loser; + } + + /* delete the old record */ + ret = (* handle->db->del)(handle->db, &node->key, 0); + if ( ret ) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + rv = SECFailure; + goto loser; + } + + /* get the public key, which we use as the database index */ + + switch (privkey->keyType) { + case NSSLOWKEYRSAKey: + newkey.data = privkey->u.rsa.modulus.data; + newkey.size = privkey->u.rsa.modulus.len; + break; + case NSSLOWKEYDSAKey: + newkey.data = privkey->u.dsa.publicValue.data; + newkey.size = privkey->u.dsa.publicValue.len; + break; + case NSSLOWKEYDHKey: + newkey.data = privkey->u.dh.publicValue.data; + newkey.size = privkey->u.dh.publicValue.len; + break; + default: + /* should we continue here and loose the key? */ + PORT_SetError(SEC_ERROR_BAD_DATABASE); + rv = SECFailure; + goto loser; + } + + rv = seckey_put_private_key(handle, &newkey, newpwitem, privkey, + nickname, PR_TRUE, new_algorithm); + + if ( rv != SECSuccess ) + { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + rv = SECFailure; + goto loser; + } + + /* next node */ + node = node->next; + } + + rv = nsslowkey_SetKeyDBPasswordAlg(handle, newpwitem, new_algorithm); + +loser: + + db_FinishTransaction(handle->db,rv != SECSuccess); + + /* free the arena */ + if ( keylist.arena ) { + PORT_FreeArena(keylist.arena, PR_FALSE); + } + + return(rv); +} + +/* + * Re-encrypt the entire key database with a new password. + * NOTE: The really should create a new database rather than doing it + * in place in the original + */ +SECStatus +nsslowkey_ChangeKeyDBPassword(NSSLOWKEYDBHandle *handle, + SECItem *oldpwitem, SECItem *newpwitem) +{ + SECStatus rv; + + if (handle == NULL) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + rv = SECFailure; + goto loser; + } + + rv = nsslowkey_CheckKeyDBPassword(handle, oldpwitem); + if ( rv != SECSuccess ) { + return(SECFailure); /* return rv? */ + } + + rv = ChangeKeyDBPasswordAlg(handle, oldpwitem, newpwitem, + nsslowkey_GetDefaultKeyDBAlg()); + +loser: + return(rv); +} + + +#define MAX_DB_SIZE 0xffff +/* + * Clear out all the keys in the existing database + */ +SECStatus +nsslowkey_ResetKeyDB(NSSLOWKEYDBHandle *handle) +{ + SECStatus rv; + int ret; + int errors = 0; + + if ( handle->db == NULL ) { + return(SECSuccess); + } + + if (handle->readOnly) { + /* set an error code */ + return SECFailure; + } + + PORT_Assert(handle->dbname != NULL); + if (handle->dbname == NULL) { + return SECFailure; + } + + (* handle->db->close)(handle->db); + 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; + } + + rv = makeGlobalSalt(handle); + if ( rv != SECSuccess ) { + errors++; + goto done; + } + + if (handle->global_salt) { + SECITEM_FreeItem(handle->global_salt,PR_TRUE); + } + handle->global_salt = GetKeyDBGlobalSalt(handle); + +done: + /* sync the database */ + ret = (* handle->db->sync)(handle->db, 0); + + return (errors == 0 ? SECSuccess : SECFailure); +} |