Source code

Revision control

Copy as Markdown

Other Tools

/* 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;
}