| /* 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) |
| { |
| return keydb->passwordKey.data ? SECSuccess : SECFailure; |
| } |
| |
| 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; |
| } |