diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /security/nss/lib/softoken/sftkpwd.c | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/nss/lib/softoken/sftkpwd.c')
-rw-r--r-- | security/nss/lib/softoken/sftkpwd.c | 1485 |
1 files changed, 1485 insertions, 0 deletions
diff --git a/security/nss/lib/softoken/sftkpwd.c b/security/nss/lib/softoken/sftkpwd.c new file mode 100644 index 0000000000..d885954085 --- /dev/null +++ b/security/nss/lib/softoken/sftkpwd.c @@ -0,0 +1,1485 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +/* + * The following code handles the storage of PKCS 11 modules used by the + * NSS. For the rest of NSS, only one kind of database handle exists: + * + * SFTKDBHandle + * + * There is one SFTKDBHandle for the each key database and one for each cert + * database. These databases are opened as associated pairs, one pair per + * slot. SFTKDBHandles are reference counted objects. + * + * Each SFTKDBHandle points to a low level database handle (SDB). This handle + * represents the underlying physical database. These objects are not + * reference counted, an are 'owned' by their respective SFTKDBHandles. + * + * + */ +#include "sftkdb.h" +#include "sftkdbti.h" +#include "pkcs11t.h" +#include "pkcs11i.h" +#include "sdb.h" +#include "prprf.h" +#include "secasn1.h" +#include "pratom.h" +#include "blapi.h" +#include "secoid.h" +#include "lowpbe.h" +#include "secdert.h" +#include "prsystem.h" +#include "lgglue.h" +#include "secerr.h" +#include "softoken.h" + +static const int NSS_MP_PBE_ITERATION_COUNT = 10000; + +static int +getPBEIterationCount(void) +{ + int c = NSS_MP_PBE_ITERATION_COUNT; + + char *val = getenv("NSS_MIN_MP_PBE_ITERATION_COUNT"); + if (val) { + int minimum = atoi(val); + if (c < minimum) { + c = minimum; + } + } + + val = getenv("NSS_MAX_MP_PBE_ITERATION_COUNT"); + if (val) { + int maximum = atoi(val); + if (c > maximum) { + c = maximum; + } + } + + return c; +} + +PRBool +sftk_isLegacyIterationCountAllowed(void) +{ + static const char *legacyCountEnvVar = + "NSS_ALLOW_LEGACY_DBM_ITERATION_COUNT"; + char *iterEnv = getenv(legacyCountEnvVar); + return (iterEnv && strcmp("0", iterEnv) != 0); +} + +/****************************************************************** + * + * Key DB password handling functions + * + * These functions manage the key db password (set, reset, initialize, use). + * + * The key is managed on 'this side' of the database. All private data is + * encrypted before it is sent to the database itself. Besides PBE's, the + * database management code can also mix in various fixed keys so the data + * in the database is no longer considered 'plain text'. + */ + +/* take string password and turn it into a key. The key is dependent + * on a global salt entry acquired from the database. This salted + * value will be based to a pkcs5 pbe function before it is used + * in an actual encryption */ +static SECStatus +sftkdb_passwordToKey(SFTKDBHandle *keydb, SECItem *salt, + const char *pw, SECItem *key) +{ + SHA1Context *cx = NULL; + SECStatus rv = SECFailure; + + if (!pw) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + key->data = PORT_Alloc(SHA1_LENGTH); + if (key->data == NULL) { + goto loser; + } + key->len = SHA1_LENGTH; + + cx = SHA1_NewContext(); + if (cx == NULL) { + goto loser; + } + SHA1_Begin(cx); + if (salt && salt->data) { + SHA1_Update(cx, salt->data, salt->len); + } + SHA1_Update(cx, (unsigned char *)pw, PORT_Strlen(pw)); + SHA1_End(cx, key->data, &key->len, key->len); + rv = SECSuccess; + +loser: + if (cx) { + SHA1_DestroyContext(cx, PR_TRUE); + } + if (rv != SECSuccess) { + if (key->data != NULL) { + PORT_ZFree(key->data, key->len); + } + key->data = NULL; + } + return rv; +} + +/* + * Cipher text stored in the database contains 3 elements: + * 1) an identifier describing the encryption algorithm. + * 2) an entry specific salt value. + * 3) the encrypted value. + * + * The following data structure represents the encrypted data in a decoded + * (but still encrypted) form. + */ +typedef struct sftkCipherValueStr sftkCipherValue; +struct sftkCipherValueStr { + PLArenaPool *arena; + SECOidTag alg; + NSSPKCS5PBEParameter *param; + SECItem salt; + SECItem value; +}; + +#define SFTK_CIPHERTEXT_VERSION 3 + +struct SFTKDBEncryptedDataInfoStr { + SECAlgorithmID algorithm; + SECItem encryptedData; +}; +typedef struct SFTKDBEncryptedDataInfoStr SFTKDBEncryptedDataInfo; + +SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate) + +const SEC_ASN1Template sftkdb_EncryptedDataInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(SFTKDBEncryptedDataInfo) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, + offsetof(SFTKDBEncryptedDataInfo, algorithm), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_OCTET_STRING, + offsetof(SFTKDBEncryptedDataInfo, encryptedData) }, + { 0 } +}; + +/* + * This parses the cipherText into cipher value. NOTE: cipherValue will point + * to data in cipherText, if cipherText is freed, cipherValue will be invalid. + */ +static SECStatus +sftkdb_decodeCipherText(const SECItem *cipherText, sftkCipherValue *cipherValue) +{ + PLArenaPool *arena = NULL; + SFTKDBEncryptedDataInfo edi; + SECStatus rv; + + PORT_Assert(cipherValue); + cipherValue->arena = NULL; + cipherValue->param = NULL; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + return SECFailure; + } + + rv = SEC_QuickDERDecodeItem(arena, &edi, sftkdb_EncryptedDataInfoTemplate, + cipherText); + if (rv != SECSuccess) { + goto loser; + } + cipherValue->alg = SECOID_GetAlgorithmTag(&edi.algorithm); + cipherValue->param = nsspkcs5_AlgidToParam(&edi.algorithm); + if (cipherValue->param == NULL) { + goto loser; + } + cipherValue->value = edi.encryptedData; + cipherValue->arena = arena; + + return SECSuccess; +loser: + if (cipherValue->param) { + nsspkcs5_DestroyPBEParameter(cipherValue->param); + cipherValue->param = NULL; + } + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + return SECFailure; +} + +/* + * unlike decode, Encode actually allocates a SECItem the caller must free + * The caller can pass an optional arena to to indicate where to place + * the resultant cipherText. + */ +static SECStatus +sftkdb_encodeCipherText(PLArenaPool *arena, sftkCipherValue *cipherValue, + SECItem **cipherText) +{ + SFTKDBEncryptedDataInfo edi; + SECAlgorithmID *algid; + SECStatus rv; + PLArenaPool *localArena = NULL; + + localArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (localArena == NULL) { + return SECFailure; + } + + algid = nsspkcs5_CreateAlgorithmID(localArena, cipherValue->alg, + cipherValue->param); + if (algid == NULL) { + rv = SECFailure; + goto loser; + } + rv = SECOID_CopyAlgorithmID(localArena, &edi.algorithm, algid); + SECOID_DestroyAlgorithmID(algid, PR_TRUE); + if (rv != SECSuccess) { + goto loser; + } + edi.encryptedData = cipherValue->value; + + *cipherText = SEC_ASN1EncodeItem(arena, NULL, &edi, + sftkdb_EncryptedDataInfoTemplate); + if (*cipherText == NULL) { + rv = SECFailure; + } + +loser: + if (localArena) { + PORT_FreeArena(localArena, PR_TRUE); + } + + return rv; +} + +/* + * Use our key to decode a cipherText block from the database. + * + * plain text is allocated by nsspkcs5_CipherData and must be freed + * with SECITEM_FreeItem by the caller. + */ +SECStatus +sftkdb_DecryptAttribute(SFTKDBHandle *handle, SECItem *passKey, + CK_OBJECT_HANDLE id, CK_ATTRIBUTE_TYPE type, + SECItem *cipherText, SECItem **plain) +{ + SECStatus rv; + sftkCipherValue cipherValue; + + /* First get the cipher type */ + *plain = NULL; + rv = sftkdb_decodeCipherText(cipherText, &cipherValue); + if (rv != SECSuccess) { + goto loser; + } + + *plain = nsspkcs5_CipherData(cipherValue.param, passKey, &cipherValue.value, + PR_FALSE, NULL); + if (*plain == NULL) { + rv = SECFailure; + goto loser; + } + + /* If we are using aes 256, we need to check authentication as well.*/ + if ((type != CKT_INVALID_TYPE) && + (cipherValue.alg == SEC_OID_PKCS5_PBES2) && + (cipherValue.param->encAlg == SEC_OID_AES_256_CBC)) { + SECItem signature; + unsigned char signData[SDB_MAX_META_DATA_LEN]; + CK_RV crv; + + /* if we get here from the old legacy db, there is clearly an + * error, don't return the plaintext */ + if (handle == NULL) { + rv = SECFailure; + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + goto loser; + } + + signature.data = signData; + signature.len = sizeof(signData); + rv = SECFailure; + /* sign sftkdb_GetAttriibuteSignature returns a crv, not an rv */ + crv = sftkdb_GetAttributeSignature(handle, handle, id, type, + &signature); + if (crv == CKR_OK) { + rv = sftkdb_VerifyAttribute(handle, passKey, CK_INVALID_HANDLE, + type, *plain, &signature); + } + if (rv != SECSuccess) { + /* handle bug 1720226 where old versions of NSS misfiled the signature + * attribute on password update */ + id |= SFTK_KEYDB_TYPE | SFTK_TOKEN_TYPE; + signature.len = sizeof(signData); + crv = sftkdb_GetAttributeSignature(handle, handle, id, type, + &signature); + if (crv != CKR_OK) { + rv = SECFailure; + PORT_SetError(SEC_ERROR_BAD_SIGNATURE); + goto loser; + } + rv = sftkdb_VerifyAttribute(handle, passKey, CK_INVALID_HANDLE, + type, *plain, &signature); + } + } + +loser: + if (cipherValue.param) { + nsspkcs5_DestroyPBEParameter(cipherValue.param); + } + if (cipherValue.arena) { + PORT_FreeArena(cipherValue.arena, PR_FALSE); + } + /* Item decrypted, but failed integrity, clear it out */ + if (*plain && rv != SECSuccess) { + SECITEM_ZfreeItem(*plain, PR_TRUE); + *plain = NULL; + } + return rv; +} + +/* If the database can't store the integrity check, it's a non-FIPS database + * and we use the old encryption scheme for it */ +static PRBool +sftkdb_useLegacyEncryption(SFTKDBHandle *handle, SDB *db) +{ + if ((handle == NULL) || (db == NULL)) { + /* this is the case where the legacy db is calling back to us to + * encrypt or decrypt attributes inside the lower level db code. + * This is because the legacy db stored keys as pkcs #8 encrypted + * blobs rather than individual encrypted attributes */ + return PR_TRUE; + } + /* currently, only the legacy db can't store meta data, but if we + * add a new db that also can't store meta data, then it to wouldn't + * be able to do the integrity checks. In both cases use the old encryption + * algorithms. */ + if ((db->sdb_flags & SDB_HAS_META) == 0) { + return PR_TRUE; + } + return PR_FALSE; +} + +/* + * encrypt a block. This function returned the encrypted ciphertext which + * the caller must free. If the caller provides an arena, cipherText will + * be allocated out of that arena. This also generated the per entry + * salt automatically. + */ +SECStatus +sftkdb_EncryptAttribute(PLArenaPool *arena, SFTKDBHandle *handle, SDB *db, + SECItem *passKey, int iterationCount, + CK_OBJECT_HANDLE id, CK_ATTRIBUTE_TYPE type, + SECItem *plainText, SECItem **cipherText) +{ + SECStatus rv; + sftkCipherValue cipherValue; + SECItem *cipher = NULL; + NSSPKCS5PBEParameter *param = NULL; + unsigned char saltData[HASH_LENGTH_MAX]; + SECItem *signature = NULL; + HASH_HashType hashType = HASH_AlgNULL; + + if (sftkdb_useLegacyEncryption(handle, db)) { + cipherValue.alg = SEC_OID_PKCS12_PBE_WITH_SHA1_AND_TRIPLE_DES_CBC; + cipherValue.salt.len = SHA1_LENGTH; + hashType = HASH_AlgSHA1; + } else { + cipherValue.alg = SEC_OID_AES_256_CBC; + cipherValue.salt.len = SHA256_LENGTH; + hashType = HASH_AlgSHA256; + } + cipherValue.salt.data = saltData; + RNG_GenerateGlobalRandomBytes(saltData, cipherValue.salt.len); + + param = nsspkcs5_NewParam(cipherValue.alg, hashType, &cipherValue.salt, + iterationCount); + if (param == NULL) { + rv = SECFailure; + goto loser; + } + cipher = nsspkcs5_CipherData(param, passKey, plainText, PR_TRUE, NULL); + if (cipher == NULL) { + rv = SECFailure; + goto loser; + } + cipherValue.value = *cipher; + cipherValue.param = param; + + rv = sftkdb_encodeCipherText(arena, &cipherValue, cipherText); + if (rv != SECSuccess) { + goto loser; + } + + /* If we are using aes 256, we need to add authentication as well */ + if ((type != CKT_INVALID_TYPE) && + (cipherValue.param->encAlg == SEC_OID_AES_256_CBC)) { + rv = sftkdb_SignAttribute(arena, handle, db, passKey, iterationCount, + CK_INVALID_HANDLE, type, plainText, + &signature); + if (rv != SECSuccess) { + goto loser; + } + rv = sftkdb_PutAttributeSignature(handle, db, id, type, + signature); + if (rv != SECSuccess) { + goto loser; + } + } + +loser: + if ((arena == NULL) && signature) { + SECITEM_ZfreeItem(signature, PR_TRUE); + } + if (cipher) { + SECITEM_FreeItem(cipher, PR_TRUE); + } + if (param) { + nsspkcs5_DestroyPBEParameter(param); + } + return rv; +} + +/* + * use the password and the pbe parameters to generate an HMAC for the + * given plain text data. This is used by sftkdb_VerifyAttribute and + * sftkdb_SignAttribute. Signature is returned in signData. The caller + * must preallocate the space in the secitem. + */ +static SECStatus +sftkdb_pbehash(SECOidTag sigOid, SECItem *passKey, + NSSPKCS5PBEParameter *param, + CK_OBJECT_HANDLE objectID, CK_ATTRIBUTE_TYPE attrType, + SECItem *plainText, SECItem *signData) +{ + SECStatus rv = SECFailure; + SECItem *key = NULL; + HMACContext *hashCx = NULL; + HASH_HashType hashType = HASH_AlgNULL; + const SECHashObject *hashObj; + unsigned char addressData[SDB_ULONG_SIZE]; + + hashType = HASH_FromHMACOid(param->encAlg); + if (hashType == HASH_AlgNULL) { + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + return SECFailure; + } + + hashObj = HASH_GetRawHashObject(hashType); + if (hashObj == NULL) { + goto loser; + } + + key = nsspkcs5_ComputeKeyAndIV(param, passKey, NULL, PR_FALSE); + if (!key) { + goto loser; + } + + hashCx = HMAC_Create(hashObj, key->data, key->len, PR_TRUE); + if (!hashCx) { + goto loser; + } + HMAC_Begin(hashCx); + /* Tie this value to a particular object. This is most important for + * the trust attributes, where and attacker could copy a value for + * 'validCA' from another cert in the database */ + sftk_ULong2SDBULong(addressData, objectID); + HMAC_Update(hashCx, addressData, SDB_ULONG_SIZE); + sftk_ULong2SDBULong(addressData, attrType); + HMAC_Update(hashCx, addressData, SDB_ULONG_SIZE); + + HMAC_Update(hashCx, plainText->data, plainText->len); + rv = HMAC_Finish(hashCx, signData->data, &signData->len, signData->len); + +loser: + if (hashCx) { + HMAC_Destroy(hashCx, PR_TRUE); + } + if (key) { + SECITEM_ZfreeItem(key, PR_TRUE); + } + return rv; +} + +/* + * Use our key to verify a signText block from the database matches + * the plainText from the database. The signText is a PKCS 5 v2 pbe. + * plainText is the plainText of the attribute. + */ +SECStatus +sftkdb_VerifyAttribute(SFTKDBHandle *handle, + SECItem *passKey, CK_OBJECT_HANDLE objectID, + CK_ATTRIBUTE_TYPE attrType, + SECItem *plainText, SECItem *signText) +{ + SECStatus rv; + sftkCipherValue signValue; + SECItem signature; + unsigned char signData[HASH_LENGTH_MAX]; + + /* First get the cipher type */ + rv = sftkdb_decodeCipherText(signText, &signValue); + if (rv != SECSuccess) { + goto loser; + } + signature.data = signData; + signature.len = sizeof(signData); + + rv = sftkdb_pbehash(signValue.alg, passKey, signValue.param, + objectID, attrType, plainText, &signature); + if (rv != SECSuccess) { + goto loser; + } + if (SECITEM_CompareItem(&signValue.value, &signature) != 0) { + PORT_SetError(SEC_ERROR_BAD_SIGNATURE); + rv = SECFailure; + } + +loser: + PORT_Memset(signData, 0, sizeof signData); + if (signValue.param) { + nsspkcs5_DestroyPBEParameter(signValue.param); + } + if (signValue.arena) { + PORT_FreeArena(signValue.arena, PR_TRUE); + } + return rv; +} + +/* + * Use our key to create a signText block the plain text of an + * attribute. The signText is a PKCS 5 v2 pbe. + */ +SECStatus +sftkdb_SignAttribute(PLArenaPool *arena, SFTKDBHandle *keyDB, SDB *db, + SECItem *passKey, int iterationCount, + CK_OBJECT_HANDLE objectID, + CK_ATTRIBUTE_TYPE attrType, + SECItem *plainText, SECItem **signature) +{ + SECStatus rv; + sftkCipherValue signValue; + NSSPKCS5PBEParameter *param = NULL; + unsigned char saltData[HASH_LENGTH_MAX]; + unsigned char signData[HASH_LENGTH_MAX]; + SECOidTag hmacAlg = SEC_OID_HMAC_SHA256; /* hash for authentication */ + SECOidTag prfAlg = SEC_OID_HMAC_SHA256; /* hash for pb key generation */ + HASH_HashType prfType; + unsigned int hmacLength; + unsigned int prfLength; + + /* this code allows us to fetch the lengths and hashes on the fly + * by simply changing the OID above */ + prfType = HASH_FromHMACOid(prfAlg); + PORT_Assert(prfType != HASH_AlgNULL); + prfLength = HASH_GetRawHashObject(prfType)->length; + PORT_Assert(prfLength <= HASH_LENGTH_MAX); + + hmacLength = HASH_GetRawHashObject(HASH_FromHMACOid(hmacAlg))->length; + PORT_Assert(hmacLength <= HASH_LENGTH_MAX); + + /* initialize our CipherValue structure */ + signValue.alg = SEC_OID_PKCS5_PBMAC1; + signValue.salt.len = prfLength; + signValue.salt.data = saltData; + signValue.value.data = signData; + signValue.value.len = hmacLength; + RNG_GenerateGlobalRandomBytes(saltData, prfLength); + + /* initialize our pkcs5 parameter */ + param = nsspkcs5_NewParam(signValue.alg, HASH_AlgSHA1, &signValue.salt, + iterationCount); + if (param == NULL) { + rv = SECFailure; + goto loser; + } + param->keyID = pbeBitGenIntegrityKey; + /* set the PKCS 5 v2 parameters, not extractable from the + * data passed into nsspkcs5_NewParam */ + param->encAlg = hmacAlg; + param->hashType = prfType; + param->keyLen = hmacLength; + rv = SECOID_SetAlgorithmID(param->poolp, ¶m->prfAlg, prfAlg, NULL); + if (rv != SECSuccess) { + goto loser; + } + + /* calculate the mac */ + rv = sftkdb_pbehash(signValue.alg, passKey, param, objectID, attrType, + plainText, &signValue.value); + if (rv != SECSuccess) { + goto loser; + } + signValue.param = param; + + /* write it out */ + rv = sftkdb_encodeCipherText(arena, &signValue, signature); + if (rv != SECSuccess) { + goto loser; + } + +loser: + PORT_Memset(signData, 0, sizeof signData); + if (param) { + nsspkcs5_DestroyPBEParameter(param); + } + return rv; +} + +/* + * safely swith the passed in key for the one caches in the keydb handle + * + * A key attached to the handle tells us the the token is logged in. + * We can used the key attached to the handle in sftkdb_EncryptAttribute + * and sftkdb_DecryptAttribute calls. + */ +static void +sftkdb_switchKeys(SFTKDBHandle *keydb, SECItem *passKey, int iterationCount) +{ + unsigned char *data; + int len; + + if (keydb->passwordLock == NULL) { + PORT_Assert(keydb->type != SFTK_KEYDB_TYPE); + return; + } + + /* an atomic pointer set would be nice */ + SKIP_AFTER_FORK(PZ_Lock(keydb->passwordLock)); + data = keydb->passwordKey.data; + len = keydb->passwordKey.len; + keydb->passwordKey.data = passKey->data; + keydb->passwordKey.len = passKey->len; + keydb->defaultIterationCount = iterationCount; + passKey->data = data; + passKey->len = len; + SKIP_AFTER_FORK(PZ_Unlock(keydb->passwordLock)); +} + +/* + * returns true if we are in a middle of a merge style update. + */ +PRBool +sftkdb_InUpdateMerge(SFTKDBHandle *keydb) +{ + return keydb->updateID ? PR_TRUE : PR_FALSE; +} + +/* + * returns true if we are looking for the password for the user's old source + * database as part of a merge style update. + */ +PRBool +sftkdb_NeedUpdateDBPassword(SFTKDBHandle *keydb) +{ + if (!sftkdb_InUpdateMerge(keydb)) { + return PR_FALSE; + } + if (keydb->updateDBIsInit && !keydb->updatePasswordKey) { + return PR_TRUE; + } + return PR_FALSE; +} + +/* + * fetch an update password key from a handle. + */ +SECItem * +sftkdb_GetUpdatePasswordKey(SFTKDBHandle *handle) +{ + SECItem *key = NULL; + + /* if we're a cert db, fetch it from our peer key db */ + if (handle->type == SFTK_CERTDB_TYPE) { + handle = handle->peerDB; + } + + /* don't have one */ + if (!handle) { + return NULL; + } + + PZ_Lock(handle->passwordLock); + if (handle->updatePasswordKey) { + key = SECITEM_DupItem(handle->updatePasswordKey); + } + PZ_Unlock(handle->passwordLock); + + return key; +} + +/* + * free the update password key from a handle. + */ +void +sftkdb_FreeUpdatePasswordKey(SFTKDBHandle *handle) +{ + SECItem *key = NULL; + + /* don't have one */ + if (!handle) { + return; + } + + /* if we're a cert db, we don't have one */ + if (handle->type == SFTK_CERTDB_TYPE) { + return; + } + + PZ_Lock(handle->passwordLock); + if (handle->updatePasswordKey) { + key = handle->updatePasswordKey; + handle->updatePasswordKey = NULL; + } + PZ_Unlock(handle->passwordLock); + + if (key) { + SECITEM_ZfreeItem(key, PR_TRUE); + } + + return; +} + +/* + * what password db we use depends heavily on the update state machine + * + * 1) no update db, return the normal database. + * 2) update db and no merge return the update db. + * 3) update db and in merge: + * return the update db if we need the update db's password, + * otherwise return our normal datbase. + */ +static SDB * +sftk_getPWSDB(SFTKDBHandle *keydb) +{ + if (!keydb->update) { + return keydb->db; + } + if (!sftkdb_InUpdateMerge(keydb)) { + return keydb->update; + } + if (sftkdb_NeedUpdateDBPassword(keydb)) { + return keydb->update; + } + return keydb->db; +} + +/* + * return success if we have a valid password entry. + * This is will show up outside of PKCS #11 as CKF_USER_PIN_INIT + * in the token flags. + */ +SECStatus +sftkdb_HasPasswordSet(SFTKDBHandle *keydb) +{ + SECItem salt, value; + unsigned char saltData[SDB_MAX_META_DATA_LEN]; + unsigned char valueData[SDB_MAX_META_DATA_LEN]; + CK_RV crv; + SDB *db; + + if (keydb == NULL) { + return SECFailure; + } + + db = sftk_getPWSDB(keydb); + if (db == NULL) { + return SECFailure; + } + + salt.data = saltData; + salt.len = sizeof(saltData); + value.data = valueData; + value.len = sizeof(valueData); + crv = (*db->sdb_GetMetaData)(db, "password", &salt, &value); + + /* If no password is set, we can update right away */ + if (((keydb->db->sdb_flags & SDB_RDONLY) == 0) && keydb->update && crv != CKR_OK) { + /* update the peer certdb if it exists */ + if (keydb->peerDB) { + sftkdb_Update(keydb->peerDB, NULL); + } + sftkdb_Update(keydb, NULL); + } + return (crv == CKR_OK) ? SECSuccess : SECFailure; +} + +/* pull out the common final part of checking a password */ +SECStatus +sftkdb_finishPasswordCheck(SFTKDBHandle *keydb, SECItem *key, + const char *pw, SECItem *value, + PRBool *tokenRemoved); + +/* + * check to see if we have the NULL password set. + * We special case the NULL password so that if you have no password set, you + * don't do thousands of hash rounds. This allows us to startup and get + * webpages without slowdown in normal mode. + */ +SECStatus +sftkdb_CheckPasswordNull(SFTKDBHandle *keydb, PRBool *tokenRemoved) +{ + /* just like sftkdb_CheckPassowd, we get the salt and value, and + * create a dbkey */ + SECStatus rv; + SECItem salt, value; + unsigned char saltData[SDB_MAX_META_DATA_LEN]; + unsigned char valueData[SDB_MAX_META_DATA_LEN]; + SECItem key; + SDB *db; + CK_RV crv; + sftkCipherValue cipherValue; + + cipherValue.param = NULL; + cipherValue.arena = NULL; + + if (keydb == NULL) { + return SECFailure; + } + + db = sftk_getPWSDB(keydb); + if (db == NULL) { + return SECFailure; + } + + key.data = NULL; + key.len = 0; + + /* get the entry from the database */ + salt.data = saltData; + salt.len = sizeof(saltData); + value.data = valueData; + value.len = sizeof(valueData); + crv = (*db->sdb_GetMetaData)(db, "password", &salt, &value); + if (crv != CKR_OK) { + rv = SECFailure; + goto done; + } + + /* get our intermediate key based on the entry salt value */ + rv = sftkdb_passwordToKey(keydb, &salt, "", &key); + if (rv != SECSuccess) { + goto done; + } + + /* First get the cipher type */ + rv = sftkdb_decodeCipherText(&value, &cipherValue); + if (rv != SECSuccess) { + goto done; + } + + if (cipherValue.param->iter != 1) { + rv = SECFailure; + goto done; + } + + rv = sftkdb_finishPasswordCheck(keydb, &key, "", &value, tokenRemoved); + +done: + if (key.data) { + PORT_ZFree(key.data, key.len); + } + if (cipherValue.param) { + nsspkcs5_DestroyPBEParameter(cipherValue.param); + } + if (cipherValue.arena) { + PORT_FreeArena(cipherValue.arena, PR_FALSE); + } + return rv; +} + +#define SFTK_PW_CHECK_STRING "password-check" +#define SFTK_PW_CHECK_LEN 14 + +/* + * check if the supplied password is valid + */ +SECStatus +sftkdb_CheckPassword(SFTKDBHandle *keydb, const char *pw, PRBool *tokenRemoved) +{ + SECStatus rv; + SECItem salt, value; + unsigned char saltData[SDB_MAX_META_DATA_LEN]; + unsigned char valueData[SDB_MAX_META_DATA_LEN]; + SECItem key; + SDB *db; + CK_RV crv; + + if (keydb == NULL) { + return SECFailure; + } + + db = sftk_getPWSDB(keydb); + if (db == NULL) { + return SECFailure; + } + + key.data = NULL; + key.len = 0; + + if (pw == NULL) + pw = ""; + + /* get the entry from the database */ + salt.data = saltData; + salt.len = sizeof(saltData); + value.data = valueData; + value.len = sizeof(valueData); + crv = (*db->sdb_GetMetaData)(db, "password", &salt, &value); + if (crv != CKR_OK) { + rv = SECFailure; + goto done; + } + + /* get our intermediate key based on the entry salt value */ + rv = sftkdb_passwordToKey(keydb, &salt, pw, &key); + if (rv != SECSuccess) { + goto done; + } + + rv = sftkdb_finishPasswordCheck(keydb, &key, pw, &value, tokenRemoved); + +done: + if (key.data) { + PORT_ZFree(key.data, key.len); + } + return rv; +} + +/* we need to pass iterationCount in case we are updating a new database + * and from an old one. */ +SECStatus +sftkdb_finishPasswordCheck(SFTKDBHandle *keydb, SECItem *key, const char *pw, + SECItem *value, PRBool *tokenRemoved) +{ + SECItem *result = NULL; + SECStatus rv; + int iterationCount = getPBEIterationCount(); + + if (*pw == 0) { + iterationCount = 1; + } else if (keydb->usesLegacyStorage && !sftk_isLegacyIterationCountAllowed()) { + iterationCount = 1; + } + + /* decrypt the entry value */ + rv = sftkdb_DecryptAttribute(keydb, key, CK_INVALID_HANDLE, + CKT_INVALID_TYPE, value, &result); + if (rv != SECSuccess) { + goto done; + } + + /* if it's what we expect, update our key in the database handle and + * return Success */ + if ((result->len == SFTK_PW_CHECK_LEN) && + PORT_Memcmp(result->data, SFTK_PW_CHECK_STRING, SFTK_PW_CHECK_LEN) == 0) { + /* + * We have a password, now lets handle any potential update cases.. + * + * First, the normal case: no update. In this case we only need the + * the password for our only DB, which we now have, we switch + * the keys and fall through. + * Second regular (non-merge) update: The target DB does not yet have + * a password initialized, we now have the password for the source DB, + * so we can switch the keys and simply update the target database. + * Merge update case: This one is trickier. + * 1) If we need the source DB password, then we just got it here. + * We need to save that password, + * then we need to check to see if we need or have the target + * database password. + * If we have it (it's the same as the source), or don't need + * it (it's not set or is ""), we can start the update now. + * If we don't have it, we need the application to get it from + * the user. Clear our sessions out to simulate a token + * removal. C_GetTokenInfo will change the token description + * and the token will still appear to be logged out. + * 2) If we already have the source DB password, this password is + * for the target database. We can now move forward with the + * update, as we now have both required passwords. + * + */ + PZ_Lock(keydb->passwordLock); + if (sftkdb_NeedUpdateDBPassword(keydb)) { + /* Squirrel this special key away. + * This has the side effect of turning sftkdb_NeedLegacyPW off, + * as well as changing which database is returned from + * SFTK_GET_PW_DB (thus effecting both sftkdb_CheckPassword() + * and sftkdb_HasPasswordSet()) */ + keydb->updatePasswordKey = SECITEM_DupItem(key); + PZ_Unlock(keydb->passwordLock); + if (keydb->updatePasswordKey == NULL) { + /* PORT_Error set by SECITEM_DupItem */ + rv = SECFailure; + goto done; + } + + /* Simulate a token removal -- we need to do this any + * any case at this point so the token name is correct. */ + *tokenRemoved = PR_TRUE; + + /* + * OK, we got the update DB password, see if we need a password + * for the target... + */ + if (sftkdb_HasPasswordSet(keydb) == SECSuccess) { + /* We have a password, do we know what the password is? + * check 1) for the password the user supplied for the + * update DB, + * and 2) for the null password. + * + * RECURSION NOTE: we are calling ourselves here. This means + * any updates, switchKeys, etc will have been completed + * if these functions return successfully, in those cases + * just exit returning Success. We don't recurse infinitely + * because we are making this call from a NeedUpdateDBPassword + * block and we've already set that update password at this + * point. */ + rv = sftkdb_CheckPassword(keydb, pw, tokenRemoved); + if (rv == SECSuccess) { + /* source and target databases have the same password, we + * are good to go */ + goto done; + } + sftkdb_CheckPasswordNull(keydb, tokenRemoved); + + /* + * Important 'NULL' code here. At this point either we + * succeeded in logging in with "" or we didn't. + * + * If we did succeed at login, our machine state will be set + * to logged in appropriately. The application will find that + * it's logged in as soon as it opens a new session. We have + * also completed the update. Life is good. + * + * If we did not succeed, well the user still successfully + * logged into the update database, since we faked the token + * removal it's just like the user logged into his smart card + * then removed it. the actual login work, so we report that + * success back to the user, but we won't actually be + * logged in. The application will find this out when it + * checks it's login state, thus triggering another password + * prompt so we can get the real target DB password. + * + * summary, we exit from here with SECSuccess no matter what. + */ + rv = SECSuccess; + goto done; + } else { + /* there is no password, just fall through to update. + * update will write the source DB's password record + * into the target DB just like it would in a non-merge + * update case. */ + } + } else { + PZ_Unlock(keydb->passwordLock); + } + /* load the keys, so the keydb can parse it's key set */ + sftkdb_switchKeys(keydb, key, iterationCount); + + /* we need to update, do it now */ + if (((keydb->db->sdb_flags & SDB_RDONLY) == 0) && keydb->update) { + /* update the peer certdb if it exists */ + if (keydb->peerDB) { + sftkdb_Update(keydb->peerDB, key); + } + sftkdb_Update(keydb, key); + } + } else { + rv = SECFailure; + /*PORT_SetError( bad password); */ + } + +done: + if (result) { + SECITEM_ZfreeItem(result, PR_TRUE); + } + return rv; +} + +/* + * return Success if the there is a cached password key. + */ +SECStatus +sftkdb_PWCached(SFTKDBHandle *keydb) +{ + SECStatus rv; + PZ_Lock(keydb->passwordLock); + rv = keydb->passwordKey.data ? SECSuccess : SECFailure; + PZ_Unlock(keydb->passwordLock); + return rv; +} + +static CK_RV +sftk_updateMacs(PLArenaPool *arena, SFTKDBHandle *handle, + CK_OBJECT_HANDLE id, SECItem *newKey, int iterationCount) +{ + SFTKDBHandle *keyHandle = handle; + SDB *keyTarget = NULL; + if (handle->type != SFTK_KEYDB_TYPE) { + keyHandle = handle->peerDB; + } + if (keyHandle == NULL) { + return CKR_OK; + } + // Old DBs don't have metadata, so we can return early here. + keyTarget = SFTK_GET_SDB(keyHandle); + if ((keyTarget->sdb_flags & SDB_HAS_META) == 0) { + return CKR_OK; + } + + id &= SFTK_OBJ_ID_MASK; + + CK_ATTRIBUTE_TYPE authAttrTypes[] = { + CKA_MODULUS, + CKA_PUBLIC_EXPONENT, + CKA_CERT_SHA1_HASH, + CKA_CERT_MD5_HASH, + CKA_TRUST_SERVER_AUTH, + CKA_TRUST_CLIENT_AUTH, + CKA_TRUST_EMAIL_PROTECTION, + CKA_TRUST_CODE_SIGNING, + CKA_TRUST_STEP_UP_APPROVED, + CKA_NSS_OVERRIDE_EXTENSIONS, + }; + const CK_ULONG authAttrTypeCount = sizeof(authAttrTypes) / sizeof(authAttrTypes[0]); + + // We don't know what attributes this object has, so we update them one at a + // time. + unsigned int i; + for (i = 0; i < authAttrTypeCount; i++) { + CK_ATTRIBUTE authAttr = { authAttrTypes[i], NULL, 0 }; + CK_RV rv = sftkdb_GetAttributeValue(handle, id, &authAttr, 1); + if (rv != CKR_OK) { + continue; + } + if ((authAttr.ulValueLen == -1) || (authAttr.ulValueLen == 0)) { + continue; + } + authAttr.pValue = PORT_ArenaAlloc(arena, authAttr.ulValueLen); + if (authAttr.pValue == NULL) { + return CKR_HOST_MEMORY; + } + rv = sftkdb_GetAttributeValue(handle, id, &authAttr, 1); + if (rv != CKR_OK) { + return rv; + } + if ((authAttr.ulValueLen == -1) || (authAttr.ulValueLen == 0)) { + return CKR_GENERAL_ERROR; + } + // GetAttributeValue just verified the old macs, so it is safe to write + // them out now. + if (authAttr.ulValueLen == sizeof(CK_ULONG) && + sftkdb_isULONGAttribute(authAttr.type)) { + CK_ULONG value = *(CK_ULONG *)authAttr.pValue; + sftk_ULong2SDBULong(authAttr.pValue, value); + authAttr.ulValueLen = SDB_ULONG_SIZE; + } + SECItem *signText; + SECItem plainText; + plainText.data = authAttr.pValue; + plainText.len = authAttr.ulValueLen; + if (sftkdb_SignAttribute(arena, handle, keyTarget, newKey, + iterationCount, id, authAttr.type, + &plainText, &signText) != SECSuccess) { + return CKR_GENERAL_ERROR; + } + if (sftkdb_PutAttributeSignature(handle, keyTarget, id, authAttr.type, + signText) != SECSuccess) { + return CKR_GENERAL_ERROR; + } + } + + return CKR_OK; +} + +static CK_RV +sftk_updateEncrypted(PLArenaPool *arena, SFTKDBHandle *keydb, + CK_OBJECT_HANDLE id, SECItem *newKey, int iterationCount) +{ + CK_ATTRIBUTE_TYPE privAttrTypes[] = { + CKA_VALUE, + CKA_PRIVATE_EXPONENT, + CKA_PRIME_1, + CKA_PRIME_2, + CKA_EXPONENT_1, + CKA_EXPONENT_2, + CKA_COEFFICIENT, + }; + const CK_ULONG privAttrCount = sizeof(privAttrTypes) / sizeof(privAttrTypes[0]); + + // We don't know what attributes this object has, so we update them one at a + // time. + unsigned int i; + for (i = 0; i < privAttrCount; i++) { + // Read the old attribute in the clear. + CK_OBJECT_HANDLE sdbId = id & SFTK_OBJ_ID_MASK; + CK_ATTRIBUTE privAttr = { privAttrTypes[i], NULL, 0 }; + CK_RV crv = sftkdb_GetAttributeValue(keydb, id, &privAttr, 1); + if (crv != CKR_OK) { + continue; + } + if ((privAttr.ulValueLen == -1) || (privAttr.ulValueLen == 0)) { + continue; + } + privAttr.pValue = PORT_ArenaAlloc(arena, privAttr.ulValueLen); + if (privAttr.pValue == NULL) { + return CKR_HOST_MEMORY; + } + crv = sftkdb_GetAttributeValue(keydb, id, &privAttr, 1); + if (crv != CKR_OK) { + return crv; + } + if ((privAttr.ulValueLen == -1) || (privAttr.ulValueLen == 0)) { + return CKR_GENERAL_ERROR; + } + SECItem plainText; + SECItem *result; + plainText.data = privAttr.pValue; + plainText.len = privAttr.ulValueLen; + if (sftkdb_EncryptAttribute(arena, keydb, keydb->db, newKey, + iterationCount, sdbId, privAttr.type, + &plainText, &result) != SECSuccess) { + return CKR_GENERAL_ERROR; + } + privAttr.pValue = result->data; + privAttr.ulValueLen = result->len; + // Clear sensitive data. + PORT_Memset(plainText.data, 0, plainText.len); + + // Write the newly encrypted attributes out directly. + keydb->newKey = newKey; + keydb->newDefaultIterationCount = iterationCount; + crv = (*keydb->db->sdb_SetAttributeValue)(keydb->db, sdbId, &privAttr, 1); + keydb->newKey = NULL; + if (crv != CKR_OK) { + return crv; + } + } + + return CKR_OK; +} + +static CK_RV +sftk_convertAttributes(SFTKDBHandle *handle, CK_OBJECT_HANDLE id, + SECItem *newKey, int iterationCount) +{ + CK_RV crv = CKR_OK; + PLArenaPool *arena = NULL; + + /* get a new arena to simplify cleanup */ + arena = PORT_NewArena(1024); + if (!arena) { + return CKR_HOST_MEMORY; + } + + /* + * first handle the MACS + */ + crv = sftk_updateMacs(arena, handle, id, newKey, iterationCount); + if (crv != CKR_OK) { + goto loser; + } + + if (handle->type == SFTK_KEYDB_TYPE) { + crv = sftk_updateEncrypted(arena, handle, id, newKey, + iterationCount); + if (crv != CKR_OK) { + goto loser; + } + } + + /* free up our mess */ + PORT_FreeArena(arena, PR_TRUE); + return CKR_OK; + +loser: + /* there may be unencrypted data, clear it out down */ + PORT_FreeArena(arena, PR_TRUE); + return crv; +} + +/* + * must be called with the old key active. + */ +CK_RV +sftkdb_convertObjects(SFTKDBHandle *handle, CK_ATTRIBUTE *template, + CK_ULONG count, SECItem *newKey, int iterationCount) +{ + SDBFind *find = NULL; + CK_ULONG idCount = SFTK_MAX_IDS; + CK_OBJECT_HANDLE ids[SFTK_MAX_IDS]; + CK_RV crv, crv2; + unsigned int i; + + crv = sftkdb_FindObjectsInit(handle, template, count, &find); + + if (crv != CKR_OK) { + return crv; + } + while ((crv == CKR_OK) && (idCount == SFTK_MAX_IDS)) { + crv = sftkdb_FindObjects(handle, find, ids, SFTK_MAX_IDS, &idCount); + for (i = 0; (crv == CKR_OK) && (i < idCount); i++) { + crv = sftk_convertAttributes(handle, ids[i], newKey, + iterationCount); + } + } + crv2 = sftkdb_FindObjectsFinal(handle, find); + if (crv == CKR_OK) + crv = crv2; + + return crv; +} + +/* + * change the database password. + */ +SECStatus +sftkdb_ChangePassword(SFTKDBHandle *keydb, + char *oldPin, char *newPin, PRBool *tokenRemoved) +{ + SECStatus rv = SECSuccess; + SECItem plainText; + SECItem newKey; + SECItem *result = NULL; + SECItem salt, value; + SFTKDBHandle *certdb; + unsigned char saltData[SDB_MAX_META_DATA_LEN]; + unsigned char valueData[SDB_MAX_META_DATA_LEN]; + int iterationCount = getPBEIterationCount(); + CK_RV crv; + SDB *db; + + if (keydb == NULL) { + return SECFailure; + } + + db = SFTK_GET_SDB(keydb); + if (db == NULL) { + return SECFailure; + } + + newKey.data = NULL; + + /* make sure we have a valid old pin */ + crv = (*keydb->db->sdb_Begin)(keydb->db); + if (crv != CKR_OK) { + rv = SECFailure; + goto loser; + } + salt.data = saltData; + salt.len = sizeof(saltData); + value.data = valueData; + value.len = sizeof(valueData); + crv = (*db->sdb_GetMetaData)(db, "password", &salt, &value); + if (crv == CKR_OK) { + rv = sftkdb_CheckPassword(keydb, oldPin, tokenRemoved); + if (rv == SECFailure) { + goto loser; + } + } else { + salt.len = SHA1_LENGTH; + RNG_GenerateGlobalRandomBytes(salt.data, salt.len); + } + + if (newPin && *newPin == 0) { + iterationCount = 1; + } else if (keydb->usesLegacyStorage && !sftk_isLegacyIterationCountAllowed()) { + iterationCount = 1; + } + + rv = sftkdb_passwordToKey(keydb, &salt, newPin, &newKey); + if (rv != SECSuccess) { + goto loser; + } + + /* + * convert encrypted entries here. + */ + crv = sftkdb_convertObjects(keydb, NULL, 0, &newKey, iterationCount); + if (crv != CKR_OK) { + rv = SECFailure; + goto loser; + } + /* fix up certdb macs */ + certdb = keydb->peerDB; + if (certdb) { + CK_ATTRIBUTE objectType = { CKA_CLASS, 0, sizeof(CK_OBJECT_CLASS) }; + CK_OBJECT_CLASS myClass = CKO_NSS_TRUST; + + objectType.pValue = &myClass; + crv = sftkdb_convertObjects(certdb, &objectType, 1, &newKey, + iterationCount); + if (crv != CKR_OK) { + rv = SECFailure; + goto loser; + } + myClass = CKO_PUBLIC_KEY; + crv = sftkdb_convertObjects(certdb, &objectType, 1, &newKey, + iterationCount); + if (crv != CKR_OK) { + rv = SECFailure; + goto loser; + } + } + + plainText.data = (unsigned char *)SFTK_PW_CHECK_STRING; + plainText.len = SFTK_PW_CHECK_LEN; + + rv = sftkdb_EncryptAttribute(NULL, keydb, keydb->db, &newKey, + iterationCount, CK_INVALID_HANDLE, + CKT_INVALID_TYPE, &plainText, &result); + if (rv != SECSuccess) { + goto loser; + } + value.data = result->data; + value.len = result->len; + crv = (*keydb->db->sdb_PutMetaData)(keydb->db, "password", &salt, &value); + if (crv != CKR_OK) { + rv = SECFailure; + goto loser; + } + crv = (*keydb->db->sdb_Commit)(keydb->db); + if (crv != CKR_OK) { + rv = SECFailure; + goto loser; + } + + keydb->newKey = NULL; + + sftkdb_switchKeys(keydb, &newKey, iterationCount); + +loser: + if (newKey.data) { + PORT_ZFree(newKey.data, newKey.len); + } + if (result) { + SECITEM_FreeItem(result, PR_TRUE); + } + if (rv != SECSuccess) { + (*keydb->db->sdb_Abort)(keydb->db); + } + + return rv; +} + +/* + * lose our cached password + */ +SECStatus +sftkdb_ClearPassword(SFTKDBHandle *keydb) +{ + SECItem oldKey; + oldKey.data = NULL; + oldKey.len = 0; + sftkdb_switchKeys(keydb, &oldKey, 1); + if (oldKey.data) { + PORT_ZFree(oldKey.data, oldKey.len); + } + return SECSuccess; +} |