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
3 changed files with 478 additions and 166 deletions

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)

View file

@ -21,6 +21,7 @@ from sat.core.i18n import _
from sat.core import exceptions
from sat.core.constants import Const as C
from sat.memory.crypto import BlockCipher, PasswordHasher
from xml.dom import minidom, NotFoundErr
from sat.core.log import getLogger
log = getLogger(__name__)
@ -41,6 +42,9 @@ class Params(object):
<general>
</general>
<individual>
<category name="General" label="%(category_general)s">
<param name="Password" value="" type="password" />
</category>
<category name="Connection" label="%(category_connection)s">
<param name="JabberID" value="name@example.org/SàT" type="string" />
<param name="Password" value="" type="password" />
@ -57,6 +61,7 @@ class Params(object):
</individual>
</params>
""" % {
'category_general': _("General"),
'category_connection': _("Connection"),
'label_NewAccount': _("Register new account"),
'label_autoconnect': _('Connect on frontend startup'),
@ -311,11 +316,16 @@ class Params(object):
d.addCallback(self.__default_ok, name, category)
d.addErrback(errback or self.__default_ko, name, category)
def _getAttr(self, node, attr, value):
""" get attribute value
def _getAttr_internal(self, node, attr, value):
"""Get attribute value.
/!\ This method would return encrypted password values.
@param node: XML param node
@param attr: name of the attribute to get (e.g.: 'value' or 'type')
@param value: user defined value"""
@param value: user defined value
@return: str
"""
if attr == 'value':
value_to_use = value if value is not None else node.getAttribute(attr) # we use value (user defined) if it exist, else we use node's default value
if node.getAttribute('type') == 'bool':
@ -323,6 +333,54 @@ class Params(object):
return value_to_use
return node.getAttribute(attr)
def _getAttr(self, node, attr, value):
"""Get attribute value (synchronous).
/!\ This method can not be used to retrieve password values.
@param node: XML param node
@param attr: name of the attribute to get (e.g.: 'value' or 'type')
@param value: user defined value
@return: str
"""
if attr == 'value' and node.getAttribute('type') == 'password':
raise exceptions.InternalError('To retrieve password values, use _asyncGetAttr instead of _getAttr')
return self._getAttr_internal(node, attr, value)
def _asyncGetAttr(self, node, attr, value, profile=None):
"""Get attribute value.
Profile passwords are returned hashed (if not empty),
other passwords are returned decrypted (if not empty).
@param node: XML param node
@param attr: name of the attribute to get (e.g.: 'value' or 'type')
@param value: user defined value
@param profile: %(doc_profile)s
@return: a deferred str
"""
value = self._getAttr_internal(node, attr, value)
if attr != 'value' or node.getAttribute('type') != 'password':
return defer.succeed(value)
param_cat = node.parentNode.getAttribute('name')
param_name = node.getAttribute('name')
if ((param_cat, param_name) == C.PROFILE_PASS_PATH) or not value:
return defer.succeed(value) # profile password and empty passwords are returned "as is"
if not profile:
raise exceptions.ProfileUnknownError('The profile is needed to decrypt a password')
try:
personal_key = self.host.memory.auth_sessions.profileGetUnique(profile)[C.MEMORY_CRYPTO_KEY]
except TypeError:
raise exceptions.InternalError(_('Trying to decrypt a password while the personal key is undefined!'))
d = BlockCipher.decrypt(personal_key, value)
def gotPlainPassword(password):
if password is None: # empty value means empty password, None means decryption failure
raise exceptions.InternalError(_('The stored password could not be decrypted!'))
return password
return d.addCallback(gotPlainPassword)
def __type_to_string(self, result):
""" convert result to string, according to its type """
if isinstance(result, bool):
@ -334,19 +392,26 @@ class Params(object):
return self.__type_to_string(self.getParamA(name, category, attr, profile_key))
def getParamA(self, name, category, attr="value", profile_key=C.PROF_KEY_NONE):
"""Helper method to get a specific attribute
@param name: name of the parameter
@param category: category of the parameter
@param attr: name of the attribute (default: "value")
@param profile: owner of the param (@ALL@ for everyone)
"""Helper method to get a specific attribute.
@return: attribute"""
/!\ This method would return encrypted password values,
to get the plain values you have to use _asyncGetParamA.
@param name: name of the parameter
@param category: category of the parameter
@param attr: name of the attribute (default: "value")
@param profile: owner of the param (@ALL@ for everyone)
@return: attribute
"""
#FIXME: looks really dirty and buggy, need to be reviewed/refactored
node = self._getParamNode(name, category)
if not node:
log.error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category})
raise exceptions.NotFound
if attr == 'value' and node[1].getAttribute('type') == 'password':
raise exceptions.InternalError('To retrieve password values, use asyncGetParamA instead of getParamA')
if node[0] == C.GENERAL:
value = self._getParam(category, name, C.GENERAL)
return self._getAttr(node[1], attr, value)
@ -372,41 +437,42 @@ class Params(object):
return d
def asyncGetParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE):
"""Helper method to get a specific attribute
@param name: name of the parameter
@param category: category of the parameter
@param attr: name of the attribute (default: "value")
@param profile: owner of the param (@ALL@ for everyone)"""
"""Helper method to get a specific attribute.
@param name: name of the parameter
@param category: category of the parameter
@param attr: name of the attribute (default: "value")
@param profile: owner of the param (@ALL@ for everyone)
@return: Deferred
"""
node = self._getParamNode(name, category)
if not node:
log.error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category})
return None
return defer.succeed(None)
if not self.checkSecurityLimit(node[1], security_limit):
log.warning(_("Trying to get parameter '%(param)s' in category '%(cat)s' without authorization!!!"
% {'param': name, 'cat': category}))
return None
return defer.succeed(None)
if node[0] == C.GENERAL:
value = self._getParam(category, name, C.GENERAL)
return defer.succeed(self._getAttr(node[1], attr, value))
return self._asyncGetAttr(node[1], attr, value)
assert node[0] == C.INDIVIDUAL
profile = self.getProfileName(profile_key)
if not profile:
log.error(_('Requesting a param for a non-existant profile'))
return defer.fail()
raise exceptions.InternalError(_('Requesting a param for a non-existant profile'))
if attr != "value":
return defer.succeed(node[1].getAttribute(attr))
try:
value = self._getParam(category, name, profile=profile)
return defer.succeed(self._getAttr(node[1], attr, value))
return self._asyncGetAttr(node[1], attr, value, profile)
except exceptions.ProfileNotInCacheError:
#We have to ask data to the storage manager
d = self.storage.getIndParam(category, name, profile)
return d.addCallback(lambda value: self._getAttr(node[1], attr, value))
return d.addCallback(lambda value: self._asyncGetAttr(node[1], attr, value, profile))
def _getParam(self, category, name, type_=C.INDIVIDUAL, cache=None, profile=C.PROF_KEY_NONE):
"""Return the param, or None if it doesn't exist
@ -625,24 +691,39 @@ class Params(object):
return categories
def setParam(self, name, value, category, security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE):
"""Set a parameter, return None if the parameter is not in param xml"""
#TODO: use different behaviour depending of the data type (e.g. password encrypted)
"""Set a parameter, return None if the parameter is not in param xml.
Parameter of type 'password' that are not the SàT profile password are
stored encrypted (if not empty). The profile password is stored hashed
(if not empty).
@param name (str): the parameter name
@param value (str): the new value
@param category (str): the parameter category
@param security_limit (int)
@param profile_key (str): %(doc_profile_key)s
@return: a deferred None value when everything is done
"""
if profile_key != C.PROF_KEY_NONE:
profile = self.getProfileName(profile_key)
if not profile:
log.error(_('Trying to set parameter for an unknown profile'))
return # TODO: throw an error
raise exceptions.ProfileUnknownError
node = self._getParamNode(name, category, '@ALL@')
if not node:
log.error(_('Requesting an unknown parameter (%(category)s/%(name)s)')
% {'category': category, 'name': name})
return
return defer.succeed(None)
if not self.checkSecurityLimit(node[1], security_limit):
log.warning(_("Trying to set parameter '%(param)s' in category '%(cat)s' without authorization!!!"
% {'param': name, 'cat': category}))
return
return defer.succeed(None)
type_ = node[1].getAttribute("type")
log.info(_("Setting parameter (%(category)s, %(name)s) = %(value)s") %
{'category': category, 'name': name, 'value': value if type_ != 'password' else '********'})
if node[0] == C.GENERAL:
self.params_gen[(category, name)] = value
@ -651,20 +732,67 @@ class Params(object):
if self.host.isConnected(profile):
self.host.bridge.paramUpdate(name, value, category, profile)
self.host.trigger.point("paramUpdateTrigger", name, value, category, node[0], profile)
return
return defer.succeed(None)
assert (node[0] == C.INDIVIDUAL)
assert (profile_key != C.PROF_KEY_NONE)
type_ = node[1].getAttribute("type")
d_list = []
if type_ == "button":
print "clique", node.toxml()
log.debug("Clicked param button %s" % node.toxml())
return defer.succeed(None)
elif type_ == "password":
try:
personal_key = self.host.memory.auth_sessions.profileGetUnique(profile)[C.MEMORY_CRYPTO_KEY]
except TypeError:
raise exceptions.InternalError(_('Trying to encrypt a password while the personal key is undefined!'))
if (category, name) == C.PROFILE_PASS_PATH:
# using 'value' as the encryption key to encrypt another encryption key... could be confusing!
d_list.append(self.host.memory.encryptPersonalData(data_key=C.MEMORY_CRYPTO_KEY,
data_value=personal_key,
crypto_key=value,
profile=profile))
d = PasswordHasher.hash(value) # profile password is hashed (empty value stays empty)
elif value: # other non empty passwords are encrypted with the personal key
d = BlockCipher.encrypt(personal_key, value)
else:
d = defer.succeed(value)
def gotFinalValue(value):
if self.host.isConnected(profile): # key can not exists if profile is not connected
self.params[profile][(category, name)] = value
self.host.bridge.paramUpdate(name, value, category, profile)
self.host.trigger.point("paramUpdateTrigger", name, value, category, node[0], profile)
self.storage.setIndParam(category, name, value, profile)
d_list.append(self.storage.setIndParam(category, name, value, profile))
d.addCallback(gotFinalValue)
return defer.DeferredList(d_list).addCallback(lambda dummy: None)
def _getNodesOfTypes(self, attr_type, node_type="@ALL@"):
"""Return all the nodes matching the given types.
TODO: using during the dev but not anymore... remove if not needed
@param attr_type (str): the attribute type (string, text, password, bool, button, list)
@param node_type (str): keyword for filtering:
@ALL@ search everywhere
@GENERAL@ only search in general type
@INDIVIDUAL@ only search in individual type
@return: dict{tuple: node}: a dict {key, value} where:
- key is a couple (attribute category, attribute name)
- value is a node
"""
ret = {}
for type_node in self.dom.documentElement.childNodes:
if (((node_type == "@ALL@" or node_type == "@GENERAL@") and type_node.nodeName == C.GENERAL) or
((node_type == "@ALL@" or node_type == "@INDIVIDUAL@") and type_node.nodeName == C.INDIVIDUAL)):
for cat_node in type_node.getElementsByTagName('category'):
cat = cat_node.getAttribute('name')
params = cat_node.getElementsByTagName("param")
for param in params:
if param.getAttribute("type") == attr_type:
ret[(cat, param.getAttribute("name"))] = param
return ret
def checkSecurityLimit(self, node, security_limit):
"""Check the given node against the given security limit.

View file

@ -18,9 +18,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sat.core.i18n import _
from sat.core.constants import Const as C
from sat.core import exceptions
from sat.core.log import getLogger
log = getLogger(__name__)
from sat.memory.crypto import BlockCipher, PasswordHasher
from twisted.enterprise import adbapi
from twisted.internet import defer
from collections import OrderedDict
@ -30,7 +32,7 @@ import os.path
import cPickle as pickle
import hashlib
CURRENT_DB_VERSION = 1
CURRENT_DB_VERSION = 2
# XXX: DATABASE schemas are used in the following way:
# - 'current' key is for the actual database schema, for a new base
@ -39,7 +41,7 @@ CURRENT_DB_VERSION = 1
# a 'current' data dict can contains the keys:
# - 'CREATE': it contains an Ordered dict with table to create as keys, and a len 2 tuple as value, where value[0] are the columns definitions and value[1] are the table constraints
# - 'INSERT': it contains an Ordered dict with table where values have to be inserted, and many tuples containing values to insert in the order of the rows (#TODO: manage named columns)
# an update data dict (the ones with a number) can contains the keys 'create', 'delete', 'cols create', 'cols delete', 'cols modify' or 'insert'. See Updater.generateUpdateData for more infos. This metho can be used to autogenerate update_data, to ease the work of the developers.
# an update data dict (the ones with a number) can contains the keys 'create', 'delete', 'cols create', 'cols delete', 'cols modify', 'insert' or 'specific'. See Updater.generateUpdateData for more infos. This method can be used to autogenerate update_data, to ease the work of the developers.
DATABASE_SCHEMAS = {
"current": {'CREATE': OrderedDict((
@ -59,7 +61,6 @@ DATABASE_SCHEMAS = {
("PRIMARY KEY (namespace, key, profile_id)", "FOREIGN KEY(profile_id) REFERENCES profiles(id) ON DELETE CASCADE"))),
('private_gen_bin', (("namespace TEXT", "key TEXT", "value BLOB"),
("PRIMARY KEY (namespace, key)",))),
('private_ind_bin', (("namespace TEXT", "key TEXT", "profile_id INTEGER", "value BLOB"),
("PRIMARY KEY (namespace, key, profile_id)", "FOREIGN KEY(profile_id) REFERENCES profiles(id) ON DELETE CASCADE")))
)),
@ -67,6 +68,8 @@ DATABASE_SCHEMAS = {
('message_types', (("'chat'",), ("'error'",), ("'groupchat'",), ("'headline'",), ("'normal'",))),
)),
},
2: {'specific': 'update2raw_v2'
},
1: {'cols create': {'history': ('extra BLOB',)}
},
}
@ -508,7 +511,7 @@ class Updater(object):
@defer.inlineCallbacks
def checkUpdates(self):
""" Check is database schema update is needed, according to DATABASE_SCHEMAS
""" Check if a database schema/content update is needed, according to DATABASE_SCHEMAS
@return: deferred which fire a list of SQL update statements, or None if no update is needed
"""
@ -519,7 +522,11 @@ class Updater(object):
local_hash = self.statementHash(local_sch)
current_hash = self.statementHash(current_sch)
if local_hash == current_hash:
# Force the update if the schemas are unchanged but a specific update is needed
force_update = local_hash == current_hash and local_version < CURRENT_DB_VERSION \
and 'specific' in DATABASE_SCHEMAS[CURRENT_DB_VERSION]
if local_hash == current_hash and not force_update:
if local_version != CURRENT_DB_VERSION:
log.warning(_("Your local schema is up-to-date, but database versions mismatch, fixing it..."))
yield self._setLocalVersion(CURRENT_DB_VERSION)
@ -532,23 +539,28 @@ class Updater(object):
# we are in a development version
update_data = self.generateUpdateData(local_sch, current_sch, False)
log.warning(_("There is a schema mismatch, but as we are on a dev version, database will be updated"))
update_raw = self.update2raw(update_data, True)
update_raw = yield self.update2raw(update_data, True)
defer.returnValue(update_raw)
else:
log.error(_(u"schema version is up-to-date, but local schema differ from expected current schema"))
update_data = self.generateUpdateData(local_sch, current_sch, True)
log.warning(_(u"Here are the commands that should fix the situation, use at your own risk (do a backup before modifying database), you can go to SàT's MUC room at sat@chat.jabberfr.org for help\n### SQL###\n%s\n### END SQL ###\n") % u'\n'.join(("%s;" % statement for statement in self.update2raw(update_data))))
update_raw = yield self.update2raw(update_data)
log.warning(_(u"Here are the commands that should fix the situation, use at your own risk (do a backup before modifying database), you can go to SàT's MUC room at sat@chat.jabberfr.org for help\n### SQL###\n%s\n### END SQL ###\n") % u'\n'.join("%s;" % statement for statement in update_raw))
raise exceptions.DatabaseError("Database mismatch")
else:
# Database is not up-to-date, we'll do the update
log.info(_("Database schema has changed, local database will be updated"))
if force_update:
log.info(_("Database content needs a specific processing, local database will be updated"))
else:
log.info(_("Database schema has changed, local database will be updated"))
update_raw = []
for version in xrange(local_version + 1, CURRENT_DB_VERSION + 1):
try:
update_data = DATABASE_SCHEMAS[version]
except KeyError:
raise exceptions.InternalError("Missing update definition (version %d)" % version)
update_raw.extend(self.update2raw(update_data))
update_raw_step = yield self.update2raw(update_data)
update_raw.extend(update_raw_step)
update_raw.append("PRAGMA user_version=%d" % CURRENT_DB_VERSION)
defer.returnValue(update_raw)
@ -693,6 +705,7 @@ class Updater(object):
'cols modify': modify_cols_data
}
@defer.inlineCallbacks
def update2raw(self, update, dev_version=False):
""" Transform update data to raw SQLite statements
@param upadte: update data as returned by generateUpdateData
@ -731,4 +744,60 @@ class Updater(object):
insert = update.get('insert', {})
ret.extend(self.insertData2Raw(insert))
return ret
specific = update.get('specific', None)
if specific:
cmds = yield getattr(self, specific)()
ret.extend(cmds)
defer.returnValue(ret)
def update2raw_v2(self):
"""Update the database from v1 to v2 (add passwords encryptions):
- the XMPP password value is re-used for the profile password (new parameter)
- the profile password is stored hashed
- the XMPP password is stored encrypted, with the profile password as key
- as there are no other stored passwords yet, it is enough, otherwise we
would need to encrypt the other passwords as it's done for XMPP password
"""
xmpp_pass_path = ('Connection', 'Password')
def encrypt_values(values):
ret = []
list_ = []
for profile_id, xmpp_password in values:
def prepare_queries(result):
try:
id_ = result[0][0]
except IndexError:
log.error("Profile of id %d is referenced in 'param_ind' but it doesn't exist!" % profile_id)
return
sat_password = xmpp_password
d1 = PasswordHasher.hash(sat_password)
personal_key = BlockCipher.getRandomKey(base64=True)
d2 = BlockCipher.encrypt(sat_password, personal_key)
d3 = BlockCipher.encrypt(personal_key, xmpp_password)
def gotValues(res):
sat_cipher, personal_cipher, xmpp_cipher = res[0][1], res[1][1], res[2][1]
ret.append("INSERT INTO param_ind(category,name,profile_id,value) VALUES ('%s','%s',%s,'%s')" %
(C.PROFILE_PASS_PATH[0], C.PROFILE_PASS_PATH[1], id_, sat_cipher))
ret.append("INSERT INTO private_ind(namespace,key,profile_id,value) VALUES ('%s','%s',%s,'%s')" %
(C.MEMORY_CRYPTO_NAMESPACE, C.MEMORY_CRYPTO_KEY, id_, personal_cipher))
ret.append("REPLACE INTO param_ind(category,name,profile_id,value) VALUES ('%s','%s',%s,'%s')" %
(xmpp_pass_path[0], xmpp_pass_path[1], id_, xmpp_cipher))
return defer.DeferredList([d1, d2, d3]).addCallback(gotValues)
d = self.dbpool.runQuery("SELECT id FROM profiles WHERE id=?", (profile_id,))
d.addCallback(prepare_queries)
list_.append(d)
d_list = defer.DeferredList(list_)
d_list.addCallback(lambda dummy: ret)
return d_list
d = self.dbpool.runQuery("SELECT profile_id,value FROM param_ind WHERE category=? AND name=?", xmpp_pass_path)
d.addCallback(encrypt_values)
return d