/* * 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: keydb.c,v 1.12 2001-12-07 01:36:17 relyea%netscape.com Exp $ */ #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 "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 /* 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) }, { 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)); } NSSLOWKEYDBHandle * nsslowkey_OpenKeyDBFilename(char *dbname, PRBool readOnly) { return(nsslowkey_OpenKeyDB(readOnly, keyDBFilenameCallback, (void *)dbname)); } 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); } #ifdef NSS_USE_KEY4_DB nsslowkey_UpdateKey3DBPass1(NSSLOWKEYDBHandle *handle) { SECStatus rv; DBT checkKey; DBT checkData; DBT saltKey; DBT saltData; DBT key; DBT data; DBT newKey; unsigned char buf[SHA1_LENGTH]; 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 != 3) { 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) { checkKey.data = KEYDB_FAKE_PW_CHECK_STRING; checkKey.size = KEYDB_FAKE_PW_CHECK_LEN; ret = (* handle->updatedb->get)(handle->updatedb, &checkKey, &checkData, 0 ); if (ret) { goto done; } } /* put global salt into the new database now */ ret = (* handle->db->put)( handle->db, &saltKey, &saltData, 0); if ( ret ) { goto done; } if (checkKey.size == KEYDB_PW_CHECK_LEN) { dbkey = decode_dbkey(&checkData, 3); if ( dbkey == NULL ) { goto done; } rv = put_dbkey(handle, &checkKey, dbkey, PR_FALSE); ret = (rv != SECSuccess); } else { ret = (* handle->db->put)(handle->db, &checkKey, &checkData, 0); } if ( ret ) { goto done; } /* 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; } } dbkey = decode_dbkey(&data, 3); if ( dbkey == NULL ) { continue; } SHA1_HashBuf(buf,key.data,key.size); newKey.data = buf; newKey.size = SHA1_LENGTH; rv = put_dbkey(handle, &newKey, dbkey, PR_FALSE); sec_destroy_dbkey(dbkey); } } while ( (* handle->updatedb->seq)(handle->updatedb, &key, &data, R_NEXT) == 0 ); done: /* sync the database */ ret = (* handle->db->sync)(handle->db, 0); (* handle->updatedb->close)(handle->updatedb); handle->updatedb = NULL; if ( oldSalt ) { SECITEM_FreeItem(oldSalt, PR_TRUE); } return(SECSuccess); } #endif 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); } NSSLOWKEYDBHandle * nsslowkey_OpenKeyDB(PRBool readOnly, NSSLOWKEYDBNameFunc namecb, void *cbarg) { NSSLOWKEYDBHandle *handle; int ret; SECStatus rv; int openflags; char *dbname = NULL; PRBool updated = PR_FALSE; handle = (NSSLOWKEYDBHandle *)PORT_ZAlloc (sizeof(NSSLOWKEYDBHandle)); if (handle == NULL) { PORT_SetError (SEC_ERROR_NO_MEMORY); return NULL; } if ( readOnly ) { openflags = O_RDONLY; } else { openflags = O_RDWR; } dbname = (*namecb)(cbarg, NSSLOWKEY_DB_FILE_VERSION); if ( dbname == NULL ) { goto loser; } handle->dbname = PORT_Strdup(dbname); handle->readOnly = readOnly; 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; goto newdb; } } newdb: /* if first open fails, try to create a new DB */ if ( handle->db == NULL ) { #ifdef NSS_USE_KEY4_DB char *dbname3 = (*namecb)(cbarg, 3); if ( readOnly ) { if (dbname3 == NULL) { goto loser; } handle->db = dbopen( dbname3, O_RDONLY, 0600, DB_HASH, 0 ); PORT_Free(handle->dbname); handle->dbname = dbname3; dbname3 = NULL; if (handle->db == NULL) { goto loser; } handle->version = nsslowkey_version(handle->db); if (handle->version != 3) { /* bogus version number record, reset the database */ (* handle->db->close)( handle->db ); handle->db = NULL; goto loser; } goto done; } #else if ( readOnly ) { goto loser; } #endif handle->db = dbopen( dbname, O_RDWR | O_CREAT | O_TRUNC, 0600, DB_HASH, 0 ); PORT_Free( dbname ); dbname = NULL; /* if create fails then we lose */ if ( handle->db == NULL ) { goto loser; } rv = makeGlobalVersion(handle); if ( rv != SECSuccess ) { goto loser; } #ifdef NSS_USE_KEY4_DB handle->updatedb = dbopen( dbname3, O_RDONLY, 0600, DB_HASH, 0 ); PORT_Free(dbname3); dbname3 = NULL; if (handle->updatedb) { /* * copy the key data, all the real work happens in pass2 */ rv = nsslowkey_UpdateKey3DBPass1(handle); if ( rv == SECSuccess ) { updated = PR_TRUE; } goto skip_v2_db; } #endif /* NSS_USE_KEY4_DB */ /* * try to update from v2 db */ dbname = (*namecb)(cbarg, 2); if ( dbname != NULL ) { handle->updatedb = dbopen( dbname, O_RDONLY, 0600, DB_HASH, 0 ); PORT_Free( dbname ); dbname = NULL; 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; } } } #ifdef NSS_USE_KEY4_DB skip_v2_db: #endif /* 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 ) { goto loser; } } #ifdef NSS_USE_KEY4_DB done: #endif 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: 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: dummy = SEC_ASN1EncodeItem(temparena, &(pki->privateKey), pk, nsslowkey_DSAPrivateKeyTemplate); if (dummy == NULL) { rv = SECFailure; goto loser; } 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: 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; rv = SEC_ASN1DecodeItem(permarena, pk, nsslowkey_RSAPrivateKeyTemplate, &pki->privateKey); break; case SEC_OID_ANSIX9_DSA_SIGNATURE: pk->keyType = NSSLOWKEYDSAKey; rv = SEC_ASN1DecodeItem(permarena, pk, nsslowkey_DSAPrivateKeyTemplate, &pki->privateKey); if (rv != SECSuccess) goto loser; rv = SEC_ASN1DecodeItem(permarena, &pk->u.dsa.params, nsslowkey_PQGParamsTemplate, &pki->algorithm.parameters); break; case SEC_OID_X942_DIFFIE_HELMAN_KEY: pk->keyType = NSSLOWKEYDHKey; 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]; 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; /* 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: return SECFailure; } 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: /* 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, O_RDWR | O_CREAT | O_TRUNC, 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); }