core, memory, bridge: added profile password + password encryption:

/!\ This changeset updates the database version to 2 and modify the database content!

Description:
    - new parameter General / Password to store the profile password
    - profile password is initialized with XMPP password value, it is stored hashed
    - bridge methods asyncCreateProfile/asyncConnect takes a new argument "password" (default = "")
    - bridge method asyncConnect returns a boolean (True = connection already established, False = connection initiated)
    - profile password is checked before initializing the XMPP connection

    - new private individual parameter to store the personal encryption key of each profile
    - personal key is randomly generated and encrypted with the profile password
    - personal key is decrypted after profile authentification and stored in a Sessions instance
    - personal key is used to encrypt/decrypt other passwords when they need to be retrieved/modified

    - modifying the profile password re-encrypt the personal key
    - Memory.setParam now returns a Deferred (the bridge method "setParam" is unchanged)
    - Memory.asyncGetParamA eventually decrypts the password, Memory.getParamA would fail on a password parameter

TODO:
    - if profile authentication is OK but XMPP authentication is KO, prompt the user for another XMPP password
    - fix the method "registerNewAccount" (and move it to a plugin)
    - remove bridge method "connect", sole "asyncConnect" should be used
This commit is contained in:
souliane 2014-05-07 16:02:23 +02:00
commit 0d28c03d27

View file

@ -34,6 +34,7 @@ from sat.memory.sqlite import SqliteStorage
from sat.memory.persistent import PersistentDict
from sat.memory.params import Params
from sat.memory.disco import Discovery
from sat.memory.crypto import BlockCipher
class Sessions(object):
@ -210,6 +211,7 @@ class Memory(object):
self._entities_cache = {} # XXX: keep presence/last resource/other data in cache
# /!\ an entity is not necessarily in roster
self.subscriptions = {}
self.auth_sessions = ProfileSessions() # remember the authenticated profiles
self.disco = Discovery(host)
fixLocalDir(False) # XXX: tmp update code, will be removed in the future
self.config = self.parseMainConf()
@ -259,7 +261,7 @@ class Memory(object):
"""Load parameters template from xml file
@param filename (str): input file
@return bool: True in case of success
@return: bool: True in case of success
"""
if not filename:
return False
@ -289,6 +291,24 @@ class Memory(object):
log.info(_("[%s] Profile session started" % profile))
self._entities_cache[profile] = {}
def newAuthSession(self, key, profile):
"""Start a new session for the authenticated profile.
The personal key is loaded encrypted from a PersistentDict before being decrypted.
@param key: the key to decrypt the personal key
@param profile: %(doc_profile)s
@return: a deferred None value
"""
def gotPersonalKey(personal_key):
"""Create the session for this profile and store the personal key"""
self.auth_sessions.newSession({C.MEMORY_CRYPTO_KEY: personal_key}, profile)
log.debug('auth session created for profile %s' % profile)
d = PersistentDict(C.MEMORY_CRYPTO_NAMESPACE, profile).load()
d.addCallback(lambda data: BlockCipher.decrypt(key, data[C.MEMORY_CRYPTO_KEY]))
return d.addCallback(gotPersonalKey)
def purgeProfileSession(self, profile):
"""Delete cache of data of profile
@param profile: %(doc_profile)s"""
@ -303,7 +323,7 @@ class Memory(object):
"""Save parameters template to xml file
@param filename (str): output file
@return bool: True in case of success
@return: bool: True in case of success
"""
if not filename:
return False
@ -320,17 +340,23 @@ class Memory(object):
def getProfilesList(self):
return self.storage.getProfilesList()
def getProfileName(self, profile_key, return_profile_keys = False):
def getProfileName(self, profile_key, return_profile_keys=False):
"""Return name of profile from keyword
@param profile_key: can be the profile name or a keywork (like @DEFAULT@)
@return: profile name or None if it doesn't exist"""
return self.params.getProfileName(profile_key, return_profile_keys)
def asyncCreateProfile(self, name):
def asyncCreateProfile(self, name, password=''):
"""Create a new profile
@param name: Profile name
@param name: profile name
@param password: profile password
@return: Deferred
"""
return self.params.asyncCreateProfile(name)
personal_key = BlockCipher.getRandomKey(base64=True) # generated once for all and saved in a PersistentDict
self.auth_sessions.newSession({C.MEMORY_CRYPTO_KEY: personal_key}, name) # will be encrypted by setParam
d = self.params.asyncCreateProfile(name)
d.addCallback(lambda dummy: self.setParam(C.PROFILE_PASS_PATH[1], password, C.PROFILE_PASS_PATH[0], profile_key=name))
return d
def asyncDeleteProfile(self, name, force=False):
"""Delete an existing profile
@ -355,7 +381,6 @@ class Memory(object):
jid_ = jid.JID(jid_s)
return self.getLastResource(jid_, profile_key) or ""
def getLastResource(self, entity_jid, profile_key):
"""Return the last resource used by an entity
@param entity_jid: entity jid
@ -502,7 +527,6 @@ class Memory(object):
"""
return self.getEntityData(entity_jid, (key,), profile_key)[key]
def delEntityCache(self, entity_jid, delete_all_resources=True, profile_key=C.PROF_KEY_NONE):
"""Remove cached data for entity
@param entity_jid: JID of the entity to delete
@ -527,6 +551,32 @@ class Memory(object):
except KeyError:
log.debug("Can't delete entity [%s]: not in cache" % entity.full())
def encryptPersonalData(self, data_key, data_value, crypto_key, profile):
"""Re-encrypt a personal data (saved to a PersistentDict).
@param data_key: key for the individual PersistentDict instance
@param data_value: the value to be encrypted
@param crypto_key: the key to encrypt the value
@param profile: %(profile_doc)s
@return: a deferred None value
"""
def gotIndMemory(data):
d = BlockCipher.encrypt(crypto_key, data_value)
def cb(cipher):
data[data_key] = cipher
return data.force(data_key)
return d.addCallback(cb)
def done(dummy):
log.debug(_('Personal data (%(ns)s, %(key)s) has been successfuly encrypted') %
{'ns': C.MEMORY_CRYPTO_NAMESPACE, 'key': data_key})
d = PersistentDict(C.MEMORY_CRYPTO_NAMESPACE, profile).load()
return d.addCallback(gotIndMemory).addCallback(done)
def addWaitingSub(self, type_, entity_jid, profile_key):
"""Called when a subcription request is received"""
profile = self.getProfileName(profile_key)