encrypted secret storage and an improved SAS verification dialog
This commit is contained in:
parent
5c80d100b7
commit
bd8ececb46
|
@ -177,6 +177,7 @@ class Config:
|
|||
'tabs_border': [opt_bool, False, _('Show tabbed notebook border in chat windows?')],
|
||||
'tabs_close_button': [opt_bool, True, _('Show close button in tab?')],
|
||||
'log_encrypted_sessions': [opt_bool, False, _('When negotiating an encrypted session, should Gajim assume you want your messages to be logged?')],
|
||||
'e2e_public_key': [opt_bool, False, _('When negotiating an encrypted session, should Gajim prefer to use public keys for identification?')],
|
||||
'chat_avatar_width': [opt_int, 52],
|
||||
'chat_avatar_height': [opt_int, 52],
|
||||
'roster_avatar_width': [opt_int, 32],
|
||||
|
|
|
@ -110,6 +110,7 @@ class ConfigPaths:
|
|||
if len(profile) > 0:
|
||||
conffile += u'.' + profile
|
||||
pidfile += u'.' + profile
|
||||
secretsfile += u'.' + profile
|
||||
pidfile += u'.pid'
|
||||
self.add_from_root('CONFIG_FILE', conffile)
|
||||
self.add_from_root('PID_FILE', pidfile)
|
||||
|
|
|
@ -1241,6 +1241,8 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
|
||||
con.send(reply)
|
||||
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
def _InitE2ECB(self, con, stanza, session):
|
||||
gajim.log.debug('InitE2ECB')
|
||||
init = stanza.getTag(name='init', namespace=common.xmpp.NS_ESESSION_INIT)
|
||||
|
@ -1248,6 +1250,8 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
|
||||
self.dispatch('SESSION_NEG', (stanza.getFrom(), session, form))
|
||||
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
def _ErrorCB(self, con, iq_obj):
|
||||
gajim.log.debug('ErrorCB')
|
||||
if iq_obj.getQueryNS() == common.xmpp.NS_VERSION:
|
||||
|
|
|
@ -62,6 +62,10 @@ class DecryptionError(Exception):
|
|||
'''A message couldn't be decrypted into usable XML'''
|
||||
pass
|
||||
|
||||
class Cancelled(Exception):
|
||||
'''The user cancelled an operation'''
|
||||
pass
|
||||
|
||||
class GajimGeneralException(Exception):
|
||||
'''This exception is our general exception'''
|
||||
def __init__(self, text=''):
|
||||
|
|
|
@ -138,14 +138,9 @@ for status in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
|
|||
|
||||
HAVE_PYCRYPTO = True
|
||||
try:
|
||||
from Crypto.PublicKey.RSA import generate
|
||||
import os
|
||||
import Crypto
|
||||
except ImportError:
|
||||
HAVE_PYCRYPTO = False
|
||||
else:
|
||||
# public key for XEP-0116
|
||||
#FIXME os.urandom is not a cryptographic PRNG
|
||||
pubkey = generate(384, os.urandom)
|
||||
|
||||
def get_nick_from_jid(jid):
|
||||
pos = jid.find('@')
|
||||
|
|
|
@ -7,11 +7,8 @@ from common import exceptions
|
|||
import random
|
||||
import string
|
||||
|
||||
import math
|
||||
import os
|
||||
import time
|
||||
|
||||
from common import dh
|
||||
import xmpp.c14n
|
||||
|
||||
import base64
|
||||
|
@ -73,6 +70,9 @@ class StanzaSession(object):
|
|||
|
||||
def cancelled_negotiation(self):
|
||||
'''A negotiation has been cancelled, so reset this session to its default state.'''
|
||||
|
||||
# XXX notify the user
|
||||
|
||||
self.status = None
|
||||
self.negotiated = {}
|
||||
|
||||
|
@ -101,6 +101,9 @@ if gajim.HAVE_PYCRYPTO:
|
|||
from Crypto.PublicKey import RSA
|
||||
from common import crypto
|
||||
|
||||
from common import dh
|
||||
import secrets
|
||||
|
||||
# an encrypted stanza negotiation has several states. i've represented them
|
||||
# as the following values in the 'status'
|
||||
# attribute of the session object:
|
||||
|
@ -231,11 +234,9 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
return compressed
|
||||
|
||||
def encrypt(self, encryptable):
|
||||
len_padding = 16 - (len(encryptable) % 16)
|
||||
if len_padding != 16:
|
||||
encryptable += len_padding * ' '
|
||||
padded = crypto.pad_to_multiple(encryptable, 16, ' ', False)
|
||||
|
||||
return self.encrypter.encrypt(encryptable)
|
||||
return self.encrypter.encrypt(padded)
|
||||
|
||||
def decrypt_stanza(self, stanza):
|
||||
c = stanza.getTag(name='c',
|
||||
|
@ -355,7 +356,7 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
def make_identity(self, form, dh_i):
|
||||
if self.negotiated['send_pubkey']:
|
||||
if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
|
||||
pubkey = gajim.interface.get_pubkey(self.conn.name)
|
||||
pubkey = secrets.secrets().my_pubkey(self.conn.name)
|
||||
fields = (pubkey.n, pubkey.e)
|
||||
|
||||
cb_fields = map(lambda f: base64.b64encode(crypto.encode_mpi(f)), fields)
|
||||
|
@ -392,7 +393,8 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
self.sas = crypto.sas_28x5(m_s, self.form_o)
|
||||
|
||||
if self.sigmai:
|
||||
self.check_identity()
|
||||
# XXX save retained secret?
|
||||
self.check_identity(lambda : ())
|
||||
|
||||
return (xmpp.DataField(name='identity', value=base64.b64encode(id_s)), \
|
||||
xmpp.DataField(name='mac', value=base64.b64encode(m_s)))
|
||||
|
@ -664,11 +666,13 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(self.k)
|
||||
self.verify_identity(form, self.d, True, 'b')
|
||||
else:
|
||||
secrets = gajim.interface.list_secrets(self.conn.name, self.jid.getStripped())
|
||||
rshashes = [self.hmac(self.n_s, rs) for rs in secrets]
|
||||
srses = secrets.secrets().retained_secrets(self.conn.name, self.jid.getStripped())
|
||||
rshashes = [self.hmac(self.n_s, rs) for (rs,v) in srses]
|
||||
|
||||
# XXX add some random fake rshashes here
|
||||
rshashes.sort()
|
||||
if not rshashes:
|
||||
# we've never spoken before, but we'll pretend we have
|
||||
rshash_size = self.hash_alg.digest_size
|
||||
rshashes.append(crypto.random_bytes(rshash_size))
|
||||
|
||||
rshashes = [base64.b64encode(rshash) for rshash in rshashes]
|
||||
result.addChild(node=xmpp.DataField(name='rshashes', value=rshashes))
|
||||
|
@ -676,7 +680,6 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
|
||||
self.form_o = ''.join(map(lambda el: xmpp.c14n.c14n(el), form.getChildren()))
|
||||
|
||||
|
||||
# MUST securely destroy K unless it will be used later to generate the final shared secret
|
||||
|
||||
for datafield in self.make_identity(result, e):
|
||||
|
@ -722,10 +725,10 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
|
||||
srs = ''
|
||||
|
||||
secrets = gajim.interface.list_secrets(self.conn.name, self.jid.getStripped())
|
||||
srses = secrets.secrets().retained_secrets(self.conn.name, self.jid.getStripped())
|
||||
rshashes = [base64.b64decode(rshash) for rshash in form.getField('rshashes').getValues()]
|
||||
|
||||
for secret in secrets:
|
||||
for (secret, verified) in srses:
|
||||
if self.hmac(self.n_o, secret) in rshashes:
|
||||
srs = secret
|
||||
break
|
||||
|
@ -766,11 +769,11 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
|
||||
def final_steps_alice(self, form):
|
||||
srs = ''
|
||||
secrets = gajim.interface.list_secrets(self.conn.name, self.jid.getStripped())
|
||||
srses = secrets.secrets().retained_secrets(self.conn.name, self.jid.getStripped())
|
||||
|
||||
srshash = base64.b64decode(form['srshash'])
|
||||
|
||||
for secret in secrets:
|
||||
for (secret, verified) in srses:
|
||||
if self.hmac(secret, 'Shared Retained Secret') == srshash:
|
||||
srs = secret
|
||||
break
|
||||
|
@ -805,10 +808,18 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
bjid = self.jid.getStripped()
|
||||
|
||||
if srs:
|
||||
gajim.interface.replace_secret(account, bjid, srs, new_srs)
|
||||
if secrets.secrets().srs_verified(account, bjid, srs):
|
||||
secrets.secrets().replace_srs(account, bjid, srs, new_srs, True)
|
||||
else:
|
||||
self.check_identity()
|
||||
gajim.interface.save_new_secret(account, bjid, new_srs)
|
||||
def _cb(verified):
|
||||
secrets.secrets().replace_srs(account, bjid, srs, new_srs, verified)
|
||||
|
||||
self.check_identity(_cb)
|
||||
else:
|
||||
def _cb(verified):
|
||||
secrets.secrets().save_new_srs(account, bjid, new_srs, verified)
|
||||
|
||||
self.check_identity(_cb)
|
||||
|
||||
def make_dhfield(self, modp_options, sigmai):
|
||||
dhs = []
|
||||
|
|
|
@ -294,6 +294,9 @@ class PassphraseDialog:
|
|||
|
||||
self.window.destroy()
|
||||
|
||||
if isinstance(self.ok_handler, tuple):
|
||||
self.ok_handler[0](passph, checked, *self.ok_handler[1:])
|
||||
else:
|
||||
self.ok_handler(passph, checked)
|
||||
|
||||
def on_cancelbutton_clicked(self, widget):
|
||||
|
@ -1065,10 +1068,13 @@ class YesNoDialog(HigDialog):
|
|||
class ConfirmationDialogCheck(ConfirmationDialog):
|
||||
'''HIG compliant confirmation dialog with checkbutton.'''
|
||||
def __init__(self, pritext, sectext='', checktext = '',
|
||||
on_response_ok = None, on_response_cancel = None):
|
||||
on_response_ok = None, on_response_cancel = None, is_modal = True):
|
||||
self.user_response_ok = on_response_ok
|
||||
self.user_response_cancel = on_response_cancel
|
||||
|
||||
HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION,
|
||||
gtk.BUTTONS_OK_CANCEL, pritext, sectext, on_response_ok,
|
||||
on_response_cancel)
|
||||
gtk.BUTTONS_OK_CANCEL, pritext, sectext, self.on_response_ok,
|
||||
self.on_response_cancel)
|
||||
|
||||
self.set_default_response(gtk.RESPONSE_OK)
|
||||
|
||||
|
@ -1077,8 +1083,19 @@ class ConfirmationDialogCheck(ConfirmationDialog):
|
|||
|
||||
self.checkbutton = gtk.CheckButton(checktext)
|
||||
self.vbox.pack_start(self.checkbutton, expand = False, fill = True)
|
||||
self.set_modal(is_modal)
|
||||
self.popup()
|
||||
|
||||
# XXX should cancel if somebody closes the dialog
|
||||
|
||||
def on_response_ok(self, widget):
|
||||
self.user_response_ok(self.is_checked())
|
||||
self.destroy()
|
||||
|
||||
def on_response_cancel(self, widget):
|
||||
self.user_response_cancel()
|
||||
self.destroy()
|
||||
|
||||
def is_checked(self):
|
||||
''' Get active state of the checkbutton '''
|
||||
return self.checkbutton.get_active()
|
||||
|
|
74
src/gajim.py
74
src/gajim.py
|
@ -126,8 +126,6 @@ from common import dbus_support
|
|||
if dbus_support.supported:
|
||||
import dbus
|
||||
|
||||
import pickle
|
||||
|
||||
if os.name == 'posix': # dl module is Unix Only
|
||||
try: # rename the process name to gajim
|
||||
import dl
|
||||
|
@ -222,7 +220,6 @@ gajimpaths = common.configpaths.gajimpaths
|
|||
|
||||
pid_filename = gajimpaths['PID_FILE']
|
||||
config_filename = gajimpaths['CONFIG_FILE']
|
||||
secrets_filename = gajimpaths['SECRETS_FILE']
|
||||
|
||||
import traceback
|
||||
import errno
|
||||
|
@ -1529,48 +1526,6 @@ class Interface:
|
|||
if os.path.isfile(path_to_file + '_notif_size_bw' + ext):
|
||||
os.remove(path_to_file + '_notif_size_bw' + ext)
|
||||
|
||||
# list the retained secrets we have for a local account and a remote jid
|
||||
def list_secrets(self, account, jid):
|
||||
f = open(secrets_filename)
|
||||
|
||||
try:
|
||||
s = pickle.load(f)[account][jid]
|
||||
except KeyError:
|
||||
s = []
|
||||
|
||||
f.close()
|
||||
return s
|
||||
|
||||
# save a new retained secret
|
||||
def save_new_secret(self, account, jid, secret):
|
||||
f = open(secrets_filename, 'r')
|
||||
secrets = pickle.load(f)
|
||||
f.close()
|
||||
|
||||
if not account in secrets:
|
||||
secrets[account] = {}
|
||||
|
||||
if not jid in secrets[account]:
|
||||
secrets[account][jid] = []
|
||||
|
||||
secrets[account][jid].append(secret)
|
||||
|
||||
f = open(secrets_filename, 'w')
|
||||
pickle.dump(secrets, f)
|
||||
f.close()
|
||||
|
||||
def replace_secret(self, account, jid, old_secret, new_secret):
|
||||
f = open(secrets_filename, 'r')
|
||||
secrets = pickle.load(f)
|
||||
f.close()
|
||||
|
||||
this_secrets = secrets[account][jid]
|
||||
this_secrets[this_secrets.index(old_secret)] = new_secret
|
||||
|
||||
f = open(secrets_filename, 'w')
|
||||
pickle.dump(secrets, f)
|
||||
f.close()
|
||||
|
||||
def add_event(self, account, jid, type_, event_args):
|
||||
'''add an event to the gajim.events var'''
|
||||
# We add it to the gajim.events queue
|
||||
|
@ -1867,6 +1822,7 @@ class Interface:
|
|||
|
||||
# encrypted session states. these are described in stanza_session.py
|
||||
|
||||
try:
|
||||
# bob responds
|
||||
if form.getType() == 'form' and 'security' in form.asDict():
|
||||
def continue_with_negotiation(*args):
|
||||
|
@ -1925,7 +1881,10 @@ class Interface:
|
|||
negotiated, not_acceptable, ask_user = session.verify_options_alice(form)
|
||||
|
||||
if session.sigmai:
|
||||
session.check_identity = lambda: negotiation.show_sas_dialog(jid, session.sas)
|
||||
def _cb(on_success):
|
||||
negotiation.show_sas_dialog(session, jid, session.sas, on_success)
|
||||
|
||||
session.check_identity = _cb
|
||||
|
||||
if ask_user:
|
||||
def accept_nondefault_options(widget):
|
||||
|
@ -1954,7 +1913,11 @@ class Interface:
|
|||
|
||||
return
|
||||
elif session.status == 'responded-e2e' and form.getType() == 'result':
|
||||
session.check_identity = lambda: negotiation.show_sas_dialog(jid, session.sas)
|
||||
|
||||
def _cb(on_success):
|
||||
negotiation.show_sas_dialog(session, jid, session.sas, on_success)
|
||||
|
||||
session.check_identity = _cb
|
||||
|
||||
try:
|
||||
session.accept_e2e_bob(form)
|
||||
|
@ -1963,7 +1926,10 @@ class Interface:
|
|||
|
||||
return
|
||||
elif session.status == 'identified-alice' and form.getType() == 'result':
|
||||
session.check_identity = lambda: negotiation.show_sas_dialog(jid, session.sas)
|
||||
def _cb(on_success):
|
||||
negotiation.show_sas_dialog(session, jid, session.sas, on_success)
|
||||
|
||||
session.check_identity = _cb
|
||||
|
||||
try:
|
||||
session.final_steps_alice(form)
|
||||
|
@ -1971,6 +1937,12 @@ class Interface:
|
|||
session.fail_bad_negotiation(details)
|
||||
|
||||
return
|
||||
except exceptions.Cancelled:
|
||||
# user cancelled the negotiation
|
||||
|
||||
session.cancelled_negotiation()
|
||||
|
||||
return
|
||||
|
||||
if form.getField('terminate'):
|
||||
if form.getField('terminate').getValue() in ('1', 'true'):
|
||||
|
@ -2824,11 +2796,5 @@ if __name__ == '__main__':
|
|||
|
||||
check_paths.check_and_possibly_create_paths()
|
||||
|
||||
# create secrets file (unless it exists)
|
||||
if not os.path.exists(secrets_filename):
|
||||
f = open(secrets_filename, 'w')
|
||||
pickle.dump({}, f)
|
||||
f.close()
|
||||
|
||||
Interface()
|
||||
gtk.main()
|
||||
|
|
|
@ -14,10 +14,22 @@ def describe_features(features):
|
|||
elif features['logging'] == 'mustnot':
|
||||
return _('- messages will not be logged')
|
||||
|
||||
def show_sas_dialog(jid, sas):
|
||||
dialogs.InformationDialog(_('''Verify the remote client's identity'''), _('''You've begun an encrypted session with %s, but it can't be guaranteed that you're talking directly to the person you think you are.
|
||||
def show_sas_dialog(session, jid, sas, on_success):
|
||||
def success_cb(checked):
|
||||
on_success(checked)
|
||||
|
||||
You should speak with them directly (in person or on the phone) and confirm that their Short Authentication String is identical to this one: %s''') % (jid, sas))
|
||||
def failure_cb():
|
||||
session.cancelled_negotiation()
|
||||
|
||||
dialogs.ConfirmationDialogCheck(_('''OK to continue with negotiation?'''),
|
||||
_('''You've begun an encrypted session with %s, but it can't be guaranteed that you're talking directly to the person you think you are.
|
||||
|
||||
You should speak with them directly (in person or on the phone) and confirm that their Short Authentication String is identical to this one: %s
|
||||
|
||||
Would you like to continue with the encrypted session?''') % (jid, sas),
|
||||
|
||||
_('Yes, I verified the Short Authentication String'),
|
||||
on_response_ok=success_cb, on_response_cancel=failure_cb, is_modal=False)
|
||||
|
||||
class FeatureNegotiationWindow:
|
||||
'''FeatureNegotiotionWindow class'''
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
from common.configpaths import gajimpaths
|
||||
|
||||
from common import crypto
|
||||
from common import exceptions
|
||||
|
||||
import dialogs
|
||||
|
||||
import os
|
||||
import pickle
|
||||
|
||||
import gtk
|
||||
|
||||
import Crypto.Cipher.AES
|
||||
import Crypto.Hash.SHA256
|
||||
import Crypto.PublicKey.RSA
|
||||
|
||||
secrets_filename = gajimpaths['SECRETS_FILE']
|
||||
secrets_cache = None
|
||||
|
||||
secrets_cipher = None
|
||||
secrets_counter = None
|
||||
|
||||
# strength of the encryption used on SECRETS_FILE
|
||||
n = 256
|
||||
|
||||
class Counter:
|
||||
def __init__(self, n, iv):
|
||||
self.n = n
|
||||
self.c = crypto.decode_mpi(iv)
|
||||
|
||||
def __call__(self):
|
||||
self.c = (self.c + 1) % (2 ** self.n)
|
||||
return crypto.encode_mpi_with_padding(self.c)
|
||||
|
||||
# return en/decrypter if it's cached, otherwise create it from the user's
|
||||
# passphrase
|
||||
def get_key(counter, passph=None):
|
||||
global secrets_cipher, secrets_counter
|
||||
|
||||
if secrets_cipher:
|
||||
return secrets_cipher
|
||||
|
||||
if not passph:
|
||||
passph, checked = dialogs.PassphraseDialog(_('Passphrase Required'),
|
||||
_('To continue, Gajim needs to access your stored secrets. Enter your passphrase')
|
||||
).run()
|
||||
|
||||
if passph == -1:
|
||||
raise exceptions.Cancelled
|
||||
|
||||
sh = Crypto.Hash.SHA256.new()
|
||||
sh.update(passph)
|
||||
key = sh.digest()
|
||||
|
||||
secrets_counter = counter
|
||||
|
||||
secrets_cipher = Crypto.Cipher.AES.new(key, Crypto.Cipher.AES.MODE_CTR,
|
||||
counter=secrets_counter)
|
||||
|
||||
return secrets_cipher
|
||||
|
||||
class Secrets:
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.srs = {}
|
||||
self.pubkeys = {}
|
||||
self.privkeys = {}
|
||||
|
||||
def _save(self):
|
||||
global secrets_cipher, secrets_counter
|
||||
|
||||
old_counter = secrets_counter.c
|
||||
|
||||
# pickle doesn't appear to have problems with trailing whitespace
|
||||
padded = crypto.pad_to_multiple(pickle.dumps(self), n / 8, ' ', False)
|
||||
encrypted = secrets_cipher.encrypt(padded)
|
||||
|
||||
f = open(secrets_filename, 'w')
|
||||
f.write(crypto.encode_mpi_with_padding(old_counter) + encrypted)
|
||||
f.close()
|
||||
|
||||
def cancel(self):
|
||||
raise exceptions.Cancelled
|
||||
|
||||
def save(self):
|
||||
passph1 = None
|
||||
|
||||
def _cont1(passph, checked):
|
||||
dialogs.PassphraseDialog(_('Confirm Passphrase'),
|
||||
_('Enter your new passphrase again for confirmation'),
|
||||
is_modal=False, ok_handler=(_cont2, passph), cancel_handler=self.cancel)
|
||||
|
||||
def _cont2(passph, checked, passph1):
|
||||
if passph != passph1:
|
||||
dialogs.PassphraseDialog(_('Create Passphrase'),
|
||||
_('Passphrases did not match.\n') +
|
||||
_('Gajim needs you to create a passphrase to encrypt stored secrets'),
|
||||
is_modal=False, ok_handler=_cont1, cancel_handler=self.cancel)
|
||||
return
|
||||
|
||||
counter = Counter(16, crypto.random_bytes(16))
|
||||
get_key(counter, passph1)
|
||||
|
||||
self._save()
|
||||
|
||||
if not os.path.exists(self.filename):
|
||||
dialogs.PassphraseDialog(_('Create Passphrase'),
|
||||
_('Gajim needs you to create a passphrase to encrypt stored secrets'),
|
||||
is_modal=False, ok_handler=_cont1, cancel_handler=self.cancel)
|
||||
else:
|
||||
self._save()
|
||||
|
||||
def retained_secrets(self, account, bare_jid):
|
||||
try:
|
||||
return self.srs[account][bare_jid]
|
||||
except KeyError:
|
||||
return []
|
||||
|
||||
# retained secrets are stored as a tuple of the secret and whether the user
|
||||
# has verified it
|
||||
def save_new_srs(self, account, jid, secret, verified):
|
||||
if not account in self.srs:
|
||||
self.srs[account] = {}
|
||||
|
||||
if not jid in self.srs[account]:
|
||||
self.srs[account][jid] = []
|
||||
|
||||
self.srs[account][jid].append((secret, verified))
|
||||
|
||||
self.save()
|
||||
|
||||
def find_srs(self, account, jid, srs):
|
||||
our_secrets = self.srs[account][jid]
|
||||
return filter(lambda (x,y): x == srs, our_secrets)[0]
|
||||
|
||||
# has the user verified this retained secret?
|
||||
def srs_verified(self, account, jid, srs):
|
||||
return self.find_srs(account, jid, srs)[1]
|
||||
|
||||
def replace_srs(self, account, jid, old_secret, new_secret, verified):
|
||||
our_secrets = self.srs[account][jid]
|
||||
|
||||
idx = our_secrets.index(self.find_srs(account, jid, old_secret))
|
||||
|
||||
our_secrets[idx] = (new_secret, verified)
|
||||
|
||||
self.save()
|
||||
|
||||
# the public key associated with 'account'
|
||||
def my_pubkey(self, account):
|
||||
try:
|
||||
pk = self.privkeys[account]
|
||||
except KeyError:
|
||||
pk = Crypto.PublicKey.RSA.generate(384, crypto.random_bytes)
|
||||
|
||||
self.privkeys[account] = pk
|
||||
self.save()
|
||||
|
||||
return pk
|
||||
|
||||
def load_secrets(filename):
|
||||
f = open(filename, 'r')
|
||||
|
||||
counter = Counter(16, f.read(16))
|
||||
|
||||
decrypted = get_key(counter).decrypt(f.read())
|
||||
|
||||
try:
|
||||
secrets = pickle.loads(decrypted)
|
||||
except:
|
||||
f.close()
|
||||
|
||||
global secrets_cipher
|
||||
|
||||
secrets_cipher = None
|
||||
|
||||
return load_secrets(filename)
|
||||
else:
|
||||
f.close()
|
||||
|
||||
return secrets
|
||||
|
||||
def secrets():
|
||||
global secrets_cache
|
||||
|
||||
if secrets_cache:
|
||||
return secrets_cache
|
||||
|
||||
if os.path.exists(secrets_filename):
|
||||
secrets_cache = load_secrets(secrets_filename)
|
||||
else:
|
||||
secrets_cache = Secrets(secrets_filename)
|
||||
|
||||
return secrets_cache
|
Loading…
Reference in New Issue