diff nss/lib/softoken/sftkpwd.c @ 0:1e5118fa0cb1

This is NSS with a Cmake Buildsyste To compile a static NSS library for Windows we've used the Chromium-NSS fork and added a Cmake buildsystem to compile it statically for Windows. See README.chromium for chromium changes and README.trustbridge for our modifications.
author Andre Heinecke <andre.heinecke@intevation.de>
date Mon, 28 Jul 2014 10:47:06 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nss/lib/softoken/sftkpwd.c	Mon Jul 28 10:47:06 2014 +0200
@@ -0,0 +1,1275 @@
+/* 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"
+  
+/******************************************************************
+ * 
+ * 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;
+
+    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(SECItem *cipherText, sftkCipherValue *cipherValue)
+{
+    PLArenaPool *arena = NULL;
+    SFTKDBEncryptedDataInfo edi;
+    SECStatus rv;
+
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if (arena == NULL) {
+	return SECFailure;
+    }
+    cipherValue->arena = NULL;
+    cipherValue->param = NULL;
+
+    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_FALSE);
+    }
+
+    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(SECItem *passKey, SECItem *cipherText, SECItem **plain) 
+{
+    SECStatus rv;
+    sftkCipherValue cipherValue;
+
+    /* First get the cipher type */
+    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;
+    } 
+
+loser:
+    if (cipherValue.param) {
+	nsspkcs5_DestroyPBEParameter(cipherValue.param);
+    }
+    if (cipherValue.arena) {
+	PORT_FreeArena(cipherValue.arena,PR_FALSE);
+    }
+    return rv;
+}
+
+/*
+ * 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, SECItem *passKey, 
+		SECItem *plainText, SECItem **cipherText) 
+{
+    SECStatus rv;
+    sftkCipherValue cipherValue;
+    SECItem *cipher = NULL;
+    NSSPKCS5PBEParameter *param = NULL;
+    unsigned char saltData[HASH_LENGTH_MAX];
+
+    cipherValue.alg = SEC_OID_PKCS12_PBE_WITH_SHA1_AND_TRIPLE_DES_CBC;
+    cipherValue.salt.len = SHA1_LENGTH;
+    cipherValue.salt.data = saltData;
+    RNG_GenerateGlobalRandomBytes(saltData,cipherValue.salt.len);
+
+    param = nsspkcs5_NewParam(cipherValue.alg, &cipherValue.salt, 1);
+    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;
+    }
+
+loser:
+    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_FreeItem(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(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:
+    if (signValue.param) {
+	nsspkcs5_DestroyPBEParameter(signValue.param);
+    }
+    if (signValue.arena) {
+	PORT_FreeArena(signValue.arena,PR_FALSE);
+    }
+    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, SECItem *passKey, 
+	 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, &signValue.salt, 1);
+    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:
+    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)
+{
+    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;
+    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;
+}
+
+#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;
+    SECItem *result = NULL;
+    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;
+    }
+
+    /* decrypt the entry value */
+    rv = sftkdb_DecryptAttribute(&key, &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_CheckPassword(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);
+
+	/* 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 (key.data) {
+	PORT_ZFree(key.data,key.len);
+    }
+    if (result) {
+	SECITEM_FreeItem(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)
+{
+    CK_RV crv = CKR_OK;
+    CK_RV crv2;
+    CK_ATTRIBUTE authAttrs[] = {
+	{CKA_MODULUS, NULL, 0},
+	{CKA_PUBLIC_EXPONENT, NULL, 0},
+	{CKA_CERT_SHA1_HASH, NULL, 0},
+	{CKA_CERT_MD5_HASH, NULL, 0},
+	{CKA_TRUST_SERVER_AUTH, NULL, 0},
+	{CKA_TRUST_CLIENT_AUTH, NULL, 0},
+	{CKA_TRUST_EMAIL_PROTECTION, NULL, 0},
+	{CKA_TRUST_CODE_SIGNING, NULL, 0},
+	{CKA_TRUST_STEP_UP_APPROVED, NULL, 0},
+	{CKA_NSS_OVERRIDE_EXTENSIONS, NULL, 0},
+    };
+    CK_ULONG authAttrCount = sizeof(authAttrs)/sizeof(CK_ATTRIBUTE);
+    int i, count;
+    SFTKDBHandle *keyHandle = handle;
+    SDB *keyTarget = NULL;
+
+    id &= SFTK_OBJ_ID_MASK;
+
+    if (handle->type != SFTK_KEYDB_TYPE) {
+	keyHandle = handle->peerDB;
+    }
+
+    if (keyHandle == NULL) {
+	return CKR_OK;
+    }
+
+    /* old DB's don't have meta data, finished with MACs */
+    keyTarget = SFTK_GET_SDB(keyHandle);
+    if ((keyTarget->sdb_flags &SDB_HAS_META) == 0) {
+	return CKR_OK;
+    }
+
+    /*
+     * STEP 1: find the MACed attributes of this object 
+     */
+    crv2 = sftkdb_GetAttributeValue(handle, id, authAttrs, authAttrCount);
+    count = 0;
+    /* allocate space for the attributes */
+    for (i=0; i < authAttrCount; i++) {
+	if ((authAttrs[i].ulValueLen == -1) || (authAttrs[i].ulValueLen == 0)){
+	    continue;
+	}
+	count++;
+        authAttrs[i].pValue = PORT_ArenaAlloc(arena,authAttrs[i].ulValueLen);
+	if (authAttrs[i].pValue == NULL) {
+	    crv = CKR_HOST_MEMORY;
+	    break;
+	}
+    }
+
+    /* if count was zero, none were found, finished with MACs */
+    if (count == 0) {
+	return CKR_OK;
+    }
+
+    crv = sftkdb_GetAttributeValue(handle, id, authAttrs, authAttrCount);
+    /* ignore error code, we expect some possible errors */
+
+    /* GetAttributeValue just verified the old macs, safe to write
+     * them out then... */
+    for (i=0; i < authAttrCount; i++) {
+	SECItem *signText;
+	SECItem plainText;
+	SECStatus rv;
+
+	if ((authAttrs[i].ulValueLen == -1) || (authAttrs[i].ulValueLen == 0)){
+	    continue;
+	}
+
+	plainText.data = authAttrs[i].pValue;
+	plainText.len = authAttrs[i].ulValueLen;
+	rv = sftkdb_SignAttribute(arena, newKey, id, 
+			authAttrs[i].type, &plainText, &signText);
+	if (rv != SECSuccess) {
+	    return CKR_GENERAL_ERROR;
+	}
+	rv = sftkdb_PutAttributeSignature(handle, keyTarget, id, 
+				authAttrs[i].type, signText);
+	if (rv != SECSuccess) {
+	    return CKR_GENERAL_ERROR;
+	}
+    }
+
+    return CKR_OK;
+}
+	
+static CK_RV
+sftk_updateEncrypted(PLArenaPool *arena, SFTKDBHandle *keydb,
+		       CK_OBJECT_HANDLE id, SECItem *newKey)
+{
+    CK_RV crv = CKR_OK;
+    CK_RV crv2;
+    CK_ATTRIBUTE *first, *last;
+    CK_ATTRIBUTE privAttrs[] = {
+	{CKA_VALUE, NULL, 0},
+	{CKA_PRIVATE_EXPONENT, NULL, 0},
+	{CKA_PRIME_1, NULL, 0},
+	{CKA_PRIME_2, NULL, 0},
+	{CKA_EXPONENT_1, NULL, 0},
+	{CKA_EXPONENT_2, NULL, 0},
+	{CKA_COEFFICIENT, NULL, 0} };
+    CK_ULONG privAttrCount = sizeof(privAttrs)/sizeof(CK_ATTRIBUTE);
+    int i, count;
+
+    /*
+     * STEP 1. Read the old attributes in the clear.
+     */
+
+    /* Get the attribute sizes.
+     *  ignore the error code, we will have unknown attributes here */
+    crv2 = sftkdb_GetAttributeValue(keydb, id, privAttrs, privAttrCount);
+
+    /*
+     * find the valid block of attributes and fill allocate space for
+     * their data */
+    first = last = NULL;
+    for (i=0; i < privAttrCount; i++) {
+         /* find the block of attributes that are appropriate for this 
+          * objects. There should only be once contiguous block, if not 
+          * there's an error.
+          *
+          * find the first and last good entry.
+          */
+	if ((privAttrs[i].ulValueLen == -1) || (privAttrs[i].ulValueLen == 0)){
+	    if (!first) continue;
+	    if (!last) {
+		/* previous entry was last good entry */
+		last= &privAttrs[i-1];
+	    }
+	    continue;
+	}
+	if (!first) {
+	    first = &privAttrs[i];
+	}
+	if (last) {
+	   /* OOPS, we've found another good entry beyond the end of the
+	    * last good entry, we need to fail here. */
+	   crv = CKR_GENERAL_ERROR;
+	   break;
+	}
+        privAttrs[i].pValue = PORT_ArenaAlloc(arena,privAttrs[i].ulValueLen);
+	if (privAttrs[i].pValue == NULL) {
+	    crv = CKR_HOST_MEMORY;
+	    break;
+	}
+    }
+    if (first == NULL) {
+	/* no valid entries found, return error based on crv2 */
+	return crv2;
+    }
+    if (last == NULL) {
+	last = &privAttrs[privAttrCount-1];
+    }
+    if (crv != CKR_OK) {
+	return crv;
+    }
+    /* read the attributes */
+    count = (last-first)+1;
+    crv = sftkdb_GetAttributeValue(keydb, id, first, count);
+    if (crv != CKR_OK) {
+	return crv;
+    }
+
+    /*
+     * STEP 2: read the encrypt the attributes with the new key.
+     */
+    for (i=0; i < count; i++) {
+	SECItem plainText;
+	SECItem *result;
+	SECStatus rv;
+
+	plainText.data = first[i].pValue;
+	plainText.len = first[i].ulValueLen;
+    	rv = sftkdb_EncryptAttribute(arena, newKey, &plainText, &result);
+	if (rv != SECSuccess) {
+	   return CKR_GENERAL_ERROR;
+	}
+	first[i].pValue = result->data;
+	first[i].ulValueLen = result->len;
+	/* clear our sensitive data out */
+	PORT_Memset(plainText.data, 0, plainText.len);
+    }
+
+
+    /*
+     * STEP 3: write the newly encrypted attributes out directly
+     */
+    id &= SFTK_OBJ_ID_MASK;
+    keydb->newKey = newKey;
+    crv = (*keydb->db->sdb_SetAttributeValue)(keydb->db, id, first, count);
+    keydb->newKey = NULL;
+
+    return crv;
+}
+	
+static CK_RV
+sftk_convertAttributes(SFTKDBHandle *handle, 
+			CK_OBJECT_HANDLE id, SECItem *newKey)
+{
+    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);
+    if (crv != CKR_OK) {
+	goto loser;
+    }
+
+    if (handle->type == SFTK_KEYDB_TYPE) {
+	crv = sftk_updateEncrypted(arena, handle, id, newKey);
+	if (crv != CKR_OK) {
+	    goto loser;
+	}
+    }
+
+    /* free up our mess */
+    /* NOTE: at this point we know we've cleared out any unencrypted data */
+    PORT_FreeArena(arena, PR_FALSE);
+    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)
+{
+    SDBFind *find = NULL;
+    CK_ULONG idCount = SFTK_MAX_IDS;
+    CK_OBJECT_HANDLE ids[SFTK_MAX_IDS];
+    CK_RV crv, crv2;
+    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);
+	}
+    }
+    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];
+    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);
+    }
+
+    rv = sftkdb_passwordToKey(keydb, &salt, newPin, &newKey);
+    if (rv != SECSuccess) {
+	goto loser;
+    }
+
+
+    /*
+     * convert encrypted entries here.
+     */
+    crv = sftkdb_convertObjects(keydb, NULL, 0, &newKey);
+    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_NETSCAPE_TRUST;
+
+	objectType.pValue = &myClass;
+	crv = sftkdb_convertObjects(certdb, &objectType, 1, &newKey);
+	if (crv != CKR_OK) {
+	    rv = SECFailure;
+	    goto loser;
+	}
+	myClass = CKO_PUBLIC_KEY;
+	crv = sftkdb_convertObjects(certdb, &objectType, 1, &newKey);
+	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, &newKey, &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);
+
+loser:
+    if (newKey.data) {
+	PORT_ZFree(newKey.data,newKey.len);
+    }
+    if (result) {
+	SECITEM_FreeItem(result, PR_FALSE);
+    }
+    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);
+    if (oldKey.data) {
+	PORT_ZFree(oldKey.data, oldKey.len);
+    }
+    return SECSuccess;
+}
+
+
This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)