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:
parent
c9f4397000
commit
0d28c03d27
1 changed files with 478 additions and 166 deletions
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue