functioning XEP-0200 implementation (hardcoded keys and counters)
This commit is contained in:
parent
4f2cd0a0fc
commit
62cf72910f
|
@ -1474,6 +1474,7 @@ class ChatControl(ChatControlBase):
|
|||
|
||||
history_menuitem = xml.get_widget('history_menuitem')
|
||||
toggle_gpg_menuitem = xml.get_widget('toggle_gpg_menuitem')
|
||||
toggle_e2e_menuitem = xml.get_widget('toggle_e2e_menuitem')
|
||||
add_to_roster_menuitem = xml.get_widget('add_to_roster_menuitem')
|
||||
send_file_menuitem = xml.get_widget('send_file_menuitem')
|
||||
compact_view_menuitem = xml.get_widget('compact_view_menuitem')
|
||||
|
@ -1492,7 +1493,7 @@ class ChatControl(ChatControlBase):
|
|||
is_sensitive = gpg_btn.get_property('sensitive')
|
||||
toggle_gpg_menuitem.set_active(isactive)
|
||||
toggle_gpg_menuitem.set_property('sensitive', is_sensitive)
|
||||
|
||||
|
||||
# If we don't have resource, we can't do file transfer
|
||||
# in transports, contact holds our info we need to disable it too
|
||||
if contact.resource and contact.jid.find('@') != -1:
|
||||
|
@ -1527,6 +1528,8 @@ class ChatControl(ChatControlBase):
|
|||
self.handlers[id] = add_to_roster_menuitem
|
||||
id = toggle_gpg_menuitem.connect('activate',
|
||||
self._on_toggle_gpg_menuitem_activate)
|
||||
id = toggle_e2e_menuitem.connect('activate',
|
||||
self._on_toggle_e2e_menuitem_activate)
|
||||
self.handlers[id] = toggle_gpg_menuitem
|
||||
id = information_menuitem.connect('activate',
|
||||
self._on_contact_information_menuitem_activate)
|
||||
|
@ -1940,10 +1943,15 @@ class ChatControl(ChatControlBase):
|
|||
tb.set_active(not tb.get_active())
|
||||
|
||||
def _on_toggle_e2e_menuitem_activate(self, widget):
|
||||
if 'security' in self.session.features and self.session.features['security'] == 'e2e':
|
||||
self.session.negotiate_e2e()
|
||||
#if 'security' in self.session.features and self.session.features['security'] == 'e2e':
|
||||
if self.session.enable_encryption:
|
||||
self.session.enable_encryption = False
|
||||
print "e2e disabled."
|
||||
# self.session.terminate_e2e()
|
||||
else:
|
||||
self.session.terminate_e2e()
|
||||
self.session.enable_encryption = True
|
||||
print "e2e enabled."
|
||||
# self.session.negotiate_e2e()
|
||||
|
||||
def got_connected(self):
|
||||
ChatControlBase.got_connected(self)
|
||||
|
|
|
@ -885,11 +885,6 @@ class Connection(ConnectionHandlers):
|
|||
if msgenc:
|
||||
msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
|
||||
|
||||
# XEP-0201
|
||||
if session:
|
||||
session.last_send = time.time()
|
||||
msg_iq.setThread(session.thread_id)
|
||||
|
||||
# JEP-0172: user_nickname
|
||||
if user_nick:
|
||||
msg_iq.setTag('nick', namespace = common.xmpp.NS_NICK).setData(
|
||||
|
@ -915,7 +910,17 @@ class Connection(ConnectionHandlers):
|
|||
if chatstate is 'composing' or msgtxt:
|
||||
chatstate_node.addChild(name = 'composing')
|
||||
|
||||
if session:
|
||||
# XEP-0201
|
||||
session.last_send = time.time()
|
||||
msg_iq.setThread(session.thread_id)
|
||||
|
||||
# XEP-0200
|
||||
if session.enable_encryption:
|
||||
msg_iq = session.encrypt_stanza(msg_iq)
|
||||
|
||||
self.connection.send(msg_iq)
|
||||
|
||||
no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')\
|
||||
.split()
|
||||
ji = gajim.get_jid_without_resource(jid)
|
||||
|
|
|
@ -37,7 +37,7 @@ from common import atom
|
|||
from common.commands import ConnectionCommands
|
||||
from common.pubsub import ConnectionPubSub
|
||||
|
||||
from common.stanza_session import StanzaSession
|
||||
from common.stanza_session import EncryptedStanzaSession
|
||||
|
||||
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
|
||||
'invisible', 'error']
|
||||
|
@ -1440,6 +1440,10 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
common.xmpp.NS_FEATURE:
|
||||
self._FeatureNegCB(con, msg, session)
|
||||
return
|
||||
|
||||
e2eTag = msg.getTag('c', namespace = common.xmpp.NS_STANZA_CRYPTO)
|
||||
if e2eTag:
|
||||
msg = session.decrypt_stanza(msg)
|
||||
|
||||
msgtxt = msg.getBody()
|
||||
msghtml = msg.getXHTML()
|
||||
|
@ -1611,7 +1615,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
return null_sessions[-1]
|
||||
|
||||
def make_new_session(self, jid, thread_id = None, type = 'chat'):
|
||||
sess = StanzaSession(self, jid, thread_id, type)
|
||||
sess = EncryptedStanzaSession(self, jid, thread_id, type)
|
||||
|
||||
if not jid in self.sessions:
|
||||
self.sessions[jid] = {}
|
||||
|
|
|
@ -6,6 +6,11 @@ from common import helpers
|
|||
import random
|
||||
import string
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Hash import HMAC, SHA256
|
||||
|
||||
import base64
|
||||
|
||||
class StanzaSession:
|
||||
def __init__(self, conn, jid, thread_id, type):
|
||||
self.conn = conn
|
||||
|
@ -33,11 +38,139 @@ class StanzaSession:
|
|||
def generate_thread_id(self):
|
||||
return "".join([random.choice(string.letters) for x in xrange(0,32)])
|
||||
|
||||
|
||||
class EncryptedStanzaSession(StanzaSession):
|
||||
def __init__(self, conn, jid, thread_id, type = 'chat'):
|
||||
StanzaSession.__init__(self, conn, jid, thread_id, type = 'chat')
|
||||
|
||||
self.n = 128
|
||||
|
||||
self.cipher = AES
|
||||
self.hash_alg = SHA256
|
||||
|
||||
self.en_key = '................'
|
||||
self.de_key = '----------------'
|
||||
|
||||
self.en_counter = 777
|
||||
self.de_counter = 777 ^ (2 ** (self.n - 1))
|
||||
|
||||
self.encrypter = self.cipher.new(self.en_key, self.cipher.MODE_CTR, counter=self.encryptcounter)
|
||||
self.decrypter = self.cipher.new(self.de_key, self.cipher.MODE_CTR, counter=self.decryptcounter)
|
||||
|
||||
self.compression = None
|
||||
|
||||
self.enable_encryption = False
|
||||
|
||||
# convert a large integer to a big-endian bitstring
|
||||
def encode_mpi(self, n):
|
||||
if n >= 256:
|
||||
return self.encode_mpi(n / 256) + chr(n % 256)
|
||||
else:
|
||||
return chr(n)
|
||||
|
||||
# convert a large integer to a big-endian bitstring, padded with \x00s to 16 bytes
|
||||
def encode_mpi_with_padding(self, n):
|
||||
ret = self.encode_mpi(n)
|
||||
|
||||
mod = len(ret) % 16
|
||||
if mod != 0:
|
||||
ret = ((16 - mod) * '\x00') + ret
|
||||
|
||||
return ret
|
||||
|
||||
# convert a big-endian bitstring to an integer
|
||||
def decode_mpi(self, s):
|
||||
if len(s) == 0:
|
||||
return 0
|
||||
else:
|
||||
return 256 * self.decode_mpi(s[:-1]) + ord(s[-1])
|
||||
|
||||
def encryptcounter(self):
|
||||
self.en_counter = (self.en_counter + 1) % 2 ** self.n
|
||||
return self.encode_mpi_with_padding(self.en_counter)
|
||||
|
||||
def decryptcounter(self):
|
||||
self.de_counter = (self.de_counter + 1) % 2 ** self.n
|
||||
return self.encode_mpi_with_padding(self.de_counter)
|
||||
|
||||
def encrypt_stanza(self, stanza):
|
||||
encryptable = filter(lambda x: x.getName() not in ('error', 'amp', 'thread'), stanza.getChildren())
|
||||
|
||||
# XXX can also encrypt contents of <error/> elements in stanzas @type = 'error'
|
||||
# (except for <defined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> child elements)
|
||||
|
||||
old_en_counter = self.en_counter
|
||||
|
||||
for element in encryptable:
|
||||
stanza.delChild(element)
|
||||
|
||||
plaintext = ''.join(map(str, encryptable))
|
||||
|
||||
m_compressed = self.compress(plaintext)
|
||||
m_final = self.encrypt(m_compressed)
|
||||
|
||||
c = stanza.NT.c
|
||||
c.setNamespace('http://www.xmpp.org/extensions/xep-0200.html#ns')
|
||||
c.NT.data = base64.b64encode(m_final)
|
||||
|
||||
# XXX check for rekey request, handle <key/> elements
|
||||
|
||||
m_content = ''.join(map(str, c.getChildren()))
|
||||
c.NT.mac = base64.b64encode(self.hmac(m_content, old_en_counter, self.en_key))
|
||||
|
||||
return stanza
|
||||
|
||||
def hmac(self, content, counter, key):
|
||||
return HMAC.new(key, content + self.encode_mpi_with_padding(counter), self.hash_alg).digest()
|
||||
|
||||
def compress(self, plaintext):
|
||||
if self.compression == None:
|
||||
return plaintext
|
||||
|
||||
def decompress(self, compressed):
|
||||
if self.compression == None:
|
||||
return compressed
|
||||
|
||||
def encrypt(self, encryptable):
|
||||
len_padding = 16 - (len(encryptable) % 16)
|
||||
encryptable += len_padding * ' '
|
||||
|
||||
return self.encrypter.encrypt(encryptable)
|
||||
|
||||
def decrypt_stanza(self, stanza):
|
||||
c = stanza.getTag(name='c', namespace='http://www.xmpp.org/extensions/xep-0200.html#ns')
|
||||
|
||||
stanza.delChild(c)
|
||||
|
||||
# contents of <c>, minus <mac>, minus whitespace
|
||||
macable = ''.join(map(str, filter(lambda x: x.getName() != 'mac', c.getChildren())))
|
||||
|
||||
received_mac = base64.b64decode(c.getTagData('mac'))
|
||||
calculated_mac = self.hmac(macable, self.de_counter, self.de_key)
|
||||
|
||||
if not calculated_mac == received_mac:
|
||||
raise 'bad signature (%s != %s)' % (repr(received_mac), repr(calculated_mac))
|
||||
|
||||
m_final = base64.b64decode(c.getTagData('data'))
|
||||
m_compressed = self.decrypt(m_final)
|
||||
plaintext = self.decompress(m_compressed)
|
||||
|
||||
try:
|
||||
parsed = xmpp.Node(node='<node>' + plaintext + '</node>')
|
||||
except:
|
||||
raise DecryptionError
|
||||
|
||||
for child in parsed.getChildren():
|
||||
stanza.addChild(node=child)
|
||||
|
||||
return stanza
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
return self.decrypter.decrypt(ciphertext)
|
||||
|
||||
def negotiate_e2e():
|
||||
|
||||
pass
|
||||
|
||||
|
||||
#<x type='form' xmlns='jabber:x:data'>
|
||||
# <field type='hidden' var='FORM_TYPE'>
|
||||
# <value>urn:xmpp:ssn</value>
|
||||
|
|
|
@ -81,6 +81,7 @@ NS_SESSION ='urn:ietf:params:xml:ns:xmpp-session'
|
|||
NS_SI ='http://jabber.org/protocol/si' # JEP-0096
|
||||
NS_SI_PUB ='http://jabber.org/protocol/sipub' # JEP-0137
|
||||
NS_SIGNED ='jabber:x:signed' # JEP-0027
|
||||
NS_STANZA_CRYPTO='http://www.xmpp.org/extensions/xep-0200.html#ns' # JEP-0200
|
||||
NS_STANZAS ='urn:ietf:params:xml:ns:xmpp-stanzas'
|
||||
NS_STREAM ='http://affinix.com/jabber/stream'
|
||||
NS_STREAMS ='http://etherx.jabber.org/streams'
|
||||
|
|
Loading…
Reference in New Issue