summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/softoken/sftkpwd.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /security/nss/lib/softoken/sftkpwd.c
parentInitial commit. (diff)
downloadfirefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz
firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip
Adding upstream version 115.8.0esr.upstream/115.8.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.c1485
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, &param->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;
+}