plugin OTR: various improvments:
- otr_data is now saved between sessions
- private key is encrypted before saving
- state change is detected (unencrypted/started/refreshed/finished) and (un)trusted (trusting is not implemented yet)
- user feedback
- fixed unencrypted message in an encrypted context warning
- fixed inject msg encoding
- minor refactoring
This commit is contained in:
parent
f5d2a31520
commit
f2b16e49b5
1 changed files with 89 additions and 41 deletions
|
|
@ -26,7 +26,12 @@ from sat.core import exceptions
|
|||
log = getLogger(__name__)
|
||||
from twisted.words.protocols.jabber import jid
|
||||
from twisted.python import failure
|
||||
from twisted.internet import defer
|
||||
import potr
|
||||
from sat.memory import persistent
|
||||
|
||||
NS_OTR = "otr_plugin"
|
||||
PRIVATE_KEY = "PRIVATE KEY"
|
||||
|
||||
DEFAULT_POLICY_FLAGS = {
|
||||
'ALLOW_V1':False,
|
||||
|
|
@ -46,14 +51,10 @@ PLUGIN_INFO = {
|
|||
}
|
||||
|
||||
|
||||
PROTOCOL='xmpp'
|
||||
MMS=1024
|
||||
|
||||
|
||||
class Context(potr.context.Context):
|
||||
|
||||
def __init__(self, host, account, peer):
|
||||
super(Context, self).__init__(account, peer)
|
||||
def __init__(self, host, account, other_jid):
|
||||
super(Context, self).__init__(account, other_jid)
|
||||
self.host = host
|
||||
|
||||
def getPolicy(self, key):
|
||||
|
|
@ -62,89 +63,136 @@ class Context(potr.context.Context):
|
|||
else:
|
||||
return False
|
||||
|
||||
def inject(self, msg, appdata=None):
|
||||
to_jid, profile = appdata
|
||||
assert isinstance(to_jid, jid.JID)
|
||||
client = self.host.getClient(profile)
|
||||
log.debug('inject(%s, appdata=%s, to=%s)' % (msg, appdata, to_jid))
|
||||
def inject(self, msg_str, appdata=None):
|
||||
assert isinstance(self.peer, jid.JID)
|
||||
msg = msg_str.decode('utf-8')
|
||||
client = self.user.client
|
||||
log.debug(u'inject(%s, appdata=%s, to=%s)' % (msg, appdata, self.peer))
|
||||
mess_data = {'message': msg,
|
||||
'type': 'chat',
|
||||
'from': client.jid,
|
||||
'to': to_jid,
|
||||
'to': self.peer,
|
||||
'subject': None,
|
||||
}
|
||||
self.host.generateMessageXML(mess_data)
|
||||
client.xmlstream.send(mess_data['xml'])
|
||||
|
||||
def setState(self, state):
|
||||
old_state = self.state
|
||||
super(Context, self).setState(state)
|
||||
log.debug("setState: %s (self = %s)" % (state, self))
|
||||
# TODO: send signal to frontends, maybe a message feedback too
|
||||
log.debug(u"setState: %s (old_state=%s) " % (state, old_state))
|
||||
|
||||
if state == potr.context.STATE_PLAINTEXT:
|
||||
feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % {'other_jid': self.peer.full()}
|
||||
elif state == potr.context.STATE_ENCRYPTED:
|
||||
try:
|
||||
fingerprint, trusted = self.getCurrentTrust()
|
||||
except TypeError:
|
||||
trusted = False
|
||||
trusted_str = _(u"trusted") if trusted else _(u"untrusted")
|
||||
|
||||
if old_state == potr.context.STATE_ENCRYPTED:
|
||||
feedback = _(u"%(trusted)s OTR conversation with %(other_jid)s REFRESHED") % {'trusted': trusted_str, 'other_jid': self.peer.full()}
|
||||
else:
|
||||
feedback = _(u"%(trusted)s Encrypted OTR conversation started with %(other_jid)s") % {'trusted': trusted_str, 'other_jid': self.peer.full()}
|
||||
elif state == potr.context.STATE_FINISHED:
|
||||
feedback = _(u"OTR conversation with %(other_jid)s is FINISHED") % {'other_jid': self.peer.full()}
|
||||
else:
|
||||
log.error(_(u"Unknown OTR state"))
|
||||
return
|
||||
|
||||
client = self.user.client
|
||||
# FIXME: newMessage should manage system message, so they don't appear as coming from the contact
|
||||
self.host.bridge.newMessage(client.jid.full(),
|
||||
feedback,
|
||||
mess_type="headline",
|
||||
to_jid=self.peer.full(),
|
||||
extra={},
|
||||
profile=client.profile)
|
||||
# TODO: send signal to frontends
|
||||
|
||||
|
||||
class Account(potr.context.Account):
|
||||
|
||||
def __init__(self, account_jid):
|
||||
global PROTOCOL, MMS
|
||||
assert isinstance(account_jid, jid.JID)
|
||||
log.debug("new account: %s" % account_jid)
|
||||
super(Account, self).__init__(account_jid, PROTOCOL, MMS)
|
||||
def __init__(self, host, client):
|
||||
log.debug(u"new account: %s" % client.jid)
|
||||
super(Account, self).__init__(client.jid, "xmpp", 1024)
|
||||
self.host = host
|
||||
self.client = client
|
||||
|
||||
def loadPrivkey(self):
|
||||
# TODO
|
||||
log.debug("loadPrivkey")
|
||||
return None
|
||||
log.debug(u"loadPrivkey")
|
||||
return self.client.otr_priv_key
|
||||
|
||||
def savePrivkey(self):
|
||||
# TODO
|
||||
log.debug("savePrivkey")
|
||||
log.debug(u"savePrivkey")
|
||||
priv_key = self.getPrivkey().serializePrivateKey()
|
||||
d = self.host.memory.encryptValue(priv_key, self.client.profile)
|
||||
def save_encrypted_key(encrypted_priv_key):
|
||||
self.client.otr_data[PRIVATE_KEY] = encrypted_priv_key
|
||||
d.addCallback(save_encrypted_key)
|
||||
|
||||
|
||||
class ContextManager(object):
|
||||
|
||||
def __init__(self, host, client):
|
||||
self.host = host
|
||||
self.account = Account(client.jid)
|
||||
self.account = Account(host, client)
|
||||
self.contexts = {}
|
||||
|
||||
def startContext(self, other):
|
||||
assert isinstance(other, jid.JID)
|
||||
if not other in self.contexts:
|
||||
self.contexts[other] = Context(self.host, self.account, other)
|
||||
return self.contexts[other]
|
||||
def startContext(self, other_jid):
|
||||
assert isinstance(other_jid, jid.JID)
|
||||
context = self.contexts.setdefault(other_jid, Context(self.host, self.account, other_jid))
|
||||
return context
|
||||
|
||||
def getContextForUser(self, other):
|
||||
log.debug("getContextForUser [%s]" % other)
|
||||
log.debug(u"getContextForUser [%s]" % other)
|
||||
return self.startContext(other)
|
||||
|
||||
|
||||
class OTR(object):
|
||||
|
||||
def __init__(self, host):
|
||||
log.info(_("OTR plugin initialization"))
|
||||
log.info(_(u"OTR plugin initialization"))
|
||||
self.host = host
|
||||
self.context_managers = {}
|
||||
host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000)
|
||||
host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100000)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def profileConnected(self, profile):
|
||||
client = self.host.getClient(profile)
|
||||
self.context_managers[profile] = ContextManager(self.host, client)
|
||||
client.otr_data = persistent.PersistentBinaryDict(NS_OTR, profile)
|
||||
yield client.otr_data.load()
|
||||
encrypted_priv_key = client.otr_data.get(PRIVATE_KEY, None)
|
||||
if encrypted_priv_key is not None:
|
||||
priv_key = yield self.host.memory.decryptValue(encrypted_priv_key, profile)
|
||||
client.otr_priv_key = potr.crypt.PK.parsePrivateKey(priv_key)[0]
|
||||
else:
|
||||
client.otr_priv_key = None
|
||||
|
||||
def _receivedTreatment(self, data, profile):
|
||||
from_jid = jid.JID(data['from'])
|
||||
log.debug("_receivedTreatment [from_jid = %s]" % from_jid)
|
||||
log.debug(u"_receivedTreatment [from_jid = %s]" % from_jid)
|
||||
otrctx = self.context_managers[profile].getContextForUser(from_jid)
|
||||
|
||||
encrypted = True
|
||||
|
||||
try:
|
||||
res = otrctx.receiveMessage(data['body'].encode('utf-8'), appdata=(from_jid, profile))
|
||||
res = otrctx.receiveMessage(data['body'].encode('utf-8'))
|
||||
except potr.context.UnencryptedMessage:
|
||||
log.warning("Received unencrypted message in an encrypted context")
|
||||
# TODO: feedback to frontends (either message or popup)
|
||||
if otrctx.state == potr.context.STATE_ENCRYPTED:
|
||||
log.warning(u"Received unencrypted message in an encrypted context (from %(jid)s)" % {'jid': from_jid.full()})
|
||||
client = self.host.getClient(profile)
|
||||
self.host.bridge.newMessage(from_jid.full(),
|
||||
_(u"WARNING: received unencrypted data in a supposedly encrypted context"),
|
||||
mess_type="headline", # FIXME: add message type for internal informations
|
||||
to_jid=client.jid.full(),
|
||||
extra={},
|
||||
profile=client.profile)
|
||||
encrypted = False
|
||||
|
||||
if encrypted == False:
|
||||
if not encrypted:
|
||||
return data
|
||||
else:
|
||||
if res[0] != None:
|
||||
|
|
@ -165,12 +213,12 @@ class OTR(object):
|
|||
to_jid.resource = self.host.memory.getLastResource(to_jid, profile) # FIXME: it's dirty, but frontends don't manage resources correctly now, refactoring is planed
|
||||
otrctx = self.context_managers[profile].getContextForUser(to_jid)
|
||||
if mess_data['type'] != 'groupchat' and otrctx.state == potr.context.STATE_ENCRYPTED:
|
||||
log.debug("encrypting message")
|
||||
otrctx.sendMessage(0, mess_data['message'].encode('utf-8'), appdata=(to_jid, profile))
|
||||
log.debug(u"encrypting message")
|
||||
otrctx.sendMessage(0, mess_data['message'].encode('utf-8'))
|
||||
client = self.host.getClient(profile)
|
||||
self.host.sendMessageToBridge(mess_data, client)
|
||||
return False
|
||||
else:
|
||||
log.debug("sending message unencrypted")
|
||||
log.debug(u"sending message unencrypted")
|
||||
return True
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue