Remove support for ESessions

Fixes #5294
This commit is contained in:
Philipp Hörist 2018-05-27 01:01:35 +02:00
parent 56fbe32b11
commit 32b74b459e
23 changed files with 42 additions and 1957 deletions

View File

@ -15,7 +15,6 @@
- python3-keyring for saving your password to your system keyring - python3-keyring for saving your password to your system keyring
- python3-pil (pillow) for support of webp avatars - python3-pil (pillow) for support of webp avatars
- python3-crypto to enable End to end encryption
- python3-gnupg to enable GPG encryption - python3-gnupg to enable GPG encryption
- For zeroconf (bonjour) you need python3-dbus - For zeroconf (bonjour) you need python3-dbus
- gir1.2-gspell-1 and hunspell-LANG where lang is your locale eg. en, fr etc - gir1.2-gspell-1 and hunspell-LANG where lang is your locale eg. en, fr etc

View File

@ -42,7 +42,6 @@ from gajim.common import app
from gajim.common import helpers from gajim.common import helpers
from gajim.common import ged from gajim.common import ged
from gajim.common import i18n from gajim.common import i18n
from gajim.common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession
from gajim.common.contacts import GC_Contact from gajim.common.contacts import GC_Contact
from nbxmpp.protocol import NS_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC from nbxmpp.protocol import NS_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC
from nbxmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO from nbxmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO
@ -207,9 +206,6 @@ class ChatControl(ChatControlBase):
session.control = self session.control = self
self.session = session self.session = session
if session.enable_encryption:
self.print_esession_details()
# Enable encryption if needed # Enable encryption if needed
self.no_autonegotiation = False self.no_autonegotiation = False
self.add_actions() self.add_actions()
@ -231,8 +227,6 @@ class ChatControl(ChatControlBase):
# Dont connect this when PrivateChatControl is used # Dont connect this when PrivateChatControl is used
app.ged.register_event_handler('update-roster-avatar', ged.GUI1, app.ged.register_event_handler('update-roster-avatar', ged.GUI1,
self._nec_update_avatar) self._nec_update_avatar)
app.ged.register_event_handler('failed-decrypt', ged.GUI1,
self._nec_failed_decrypt)
app.ged.register_event_handler('chatstate-received', ged.GUI1, app.ged.register_event_handler('chatstate-received', ged.GUI1,
self._nec_chatstate_received) self._nec_chatstate_received)
app.ged.register_event_handler('caps-received', ged.GUI1, app.ged.register_event_handler('caps-received', ged.GUI1,
@ -920,53 +914,6 @@ class ChatControl(ChatControlBase):
chatstate=chatstate_to_send, xhtml=xhtml, chatstate=chatstate_to_send, xhtml=xhtml,
process_commands=process_commands, attention=attention) process_commands=process_commands, attention=attention)
def on_cancel_session_negotiation(self):
msg = _('Session negotiation cancelled')
ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
def print_archiving_session_details(self):
"""
Print esession settings to textview
"""
archiving = bool(self.session) and isinstance(self.session,
ArchivingStanzaSession) and self.session.archiving
if archiving:
msg = _('This session WILL be archived on server')
else:
msg = _('This session WILL NOT be archived on server')
ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
def print_esession_details(self):
"""
Print esession settings to textview
"""
e2e_is_active = bool(self.session) and self.session.enable_encryption
if e2e_is_active:
msg = _('This session is encrypted')
if self.session.is_loggable():
msg += _(' and WILL be logged')
else:
msg += _(' and WILL NOT be logged')
ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
if not self.session.verified_identity:
ChatControlBase.print_conversation_line(self, _("Remote contact's identity not verified. Click the shield button for more details."), 'status', '', None)
else:
msg = _('end-to-end encryption disabled')
ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
self._show_lock_image(e2e_is_active, 'E2E',
self.session and self.session.verified_identity)
def print_session_details(self, old_session=None):
if isinstance(self.session, EncryptedStanzaSession) or \
(old_session and isinstance(old_session, EncryptedStanzaSession)):
self.print_esession_details()
elif isinstance(self.session, ArchivingStanzaSession):
self.print_archiving_session_details()
def get_our_nick(self): def get_our_nick(self):
return app.nicks[self.account] return app.nicks[self.account]
@ -1186,8 +1133,6 @@ class ChatControl(ChatControlBase):
if self.TYPE_ID == message_control.TYPE_CHAT: if self.TYPE_ID == message_control.TYPE_CHAT:
app.ged.remove_event_handler('update-roster-avatar', ged.GUI1, app.ged.remove_event_handler('update-roster-avatar', ged.GUI1,
self._nec_update_avatar) self._nec_update_avatar)
app.ged.remove_event_handler('failed-decrypt', ged.GUI1,
self._nec_failed_decrypt)
app.ged.remove_event_handler('chatstate-received', ged.GUI1, app.ged.remove_event_handler('chatstate-received', ged.GUI1,
self._nec_chatstate_received) self._nec_chatstate_received)
app.ged.remove_event_handler('caps-received', ged.GUI1, app.ged.remove_event_handler('caps-received', ged.GUI1,
@ -1482,57 +1427,6 @@ class ChatControl(ChatControlBase):
""" """
dialogs.TransformChatToMUC(self.account, [self.contact.jid]) dialogs.TransformChatToMUC(self.account, [self.contact.jid])
def activate_esessions(self):
if not (self.session and self.session.enable_encryption):
self.begin_e2e_negotiation()
def terminate_esessions(self):
if not (self.session and self.session.enable_encryption):
return
# e2e was enabled, disable it
jid = str(self.session.jid)
thread_id = self.session.thread_id
self.session.terminate_e2e()
app.connections[self.account].delete_session(jid, thread_id)
# presumably the user had a good reason to shut it off, so
# disable autonegotiation too
self.no_autonegotiation = True
def begin_negotiation(self):
self.no_autonegotiation = True
if not self.session:
fjid = self.contact.get_full_jid()
new_sess = app.connections[self.account].make_new_session(fjid, type_=self.type_id)
self.set_session(new_sess)
def begin_e2e_negotiation(self):
self.begin_negotiation()
self.session.resource = self.contact.resource
self.session.negotiate_e2e(False)
def _nec_failed_decrypt(self, obj):
if obj.session != self.session:
return
details = _('Unable to decrypt message from %s\nIt may have been '
'tampered with.') % obj.fjid
self.print_conversation_line(details, 'status', '', obj.timestamp)
# terminate the session
thread_id = self.session.thread_id
self.session.terminate_e2e()
obj.conn.delete_session(obj.fjid, thread_id)
# restart the session
self.begin_e2e_negotiation()
# Stop emission so it doesn't go to gui_interface
return True
def got_connected(self): def got_connected(self):
ChatControlBase.got_connected(self) ChatControlBase.got_connected(self)
# Refreshing contact # Refreshing contact

View File

@ -451,9 +451,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
plugin = app.plugin_manager.encryption_plugins[encryption] plugin = app.plugin_manager.encryption_plugins[encryption]
if not plugin.activate_encryption(self): if not plugin.activate_encryption(self):
return return
else:
if not self.widget_name == 'groupchat_control':
self.terminate_esessions()
action.set_state(param) action.set_state(param)
self.set_encryption_state(encryption) self.set_encryption_state(encryption)
self.set_encryption_menu_icon() self.set_encryption_menu_icon()

View File

@ -147,7 +147,6 @@ caps_hash = {}
_dependencies = { _dependencies = {
'PYTHON-DBUS': False, 'PYTHON-DBUS': False,
'PYBONJOUR': False, 'PYBONJOUR': False,
'PYCRYPTO': False,
'PYGPG': False, 'PYGPG': False,
'GPG_BINARY': False, 'GPG_BINARY': False,
'FARSTREAM': False, 'FARSTREAM': False,
@ -188,13 +187,6 @@ def detect_dependencies():
except Exception: except Exception:
pass pass
# PYCRYPTO
try:
import Crypto
_dependencies['PYCRYPTO'] = True
except ImportError:
pass
# python-gnupg # python-gnupg
try: try:
import gnupg import gnupg

View File

@ -338,8 +338,6 @@ class Config:
'keyid': [ opt_str, '', '', True ], 'keyid': [ opt_str, '', '', True ],
'gpg_sign_presence': [ opt_bool, True, _('If disabled, don\'t sign presences with GPG key, even if GPG is configured.') ], 'gpg_sign_presence': [ opt_bool, True, _('If disabled, don\'t sign presences with GPG key, even if GPG is configured.') ],
'keyname': [ opt_str, '', '', True ], 'keyname': [ opt_str, '', '', True ],
'enable_esessions': [opt_bool, True, _('Enable ESessions encryption for this account.'), True],
'autonegotiate_esessions': [opt_bool, False, _('Should Gajim automatically start an encrypted session when possible?')],
'allow_plaintext_connection': [ opt_bool, False, _('Allow plaintext connections')], 'allow_plaintext_connection': [ opt_bool, False, _('Allow plaintext connections')],
'tls_version': [ opt_str, '1.2', '' ], 'tls_version': [ opt_str, '1.2', '' ],
'cipher_list': [ opt_str, 'HIGH:!aNULL:RC4-SHA', '' ], 'cipher_list': [ opt_str, 'HIGH:!aNULL:RC4-SHA', '' ],

View File

@ -340,10 +340,7 @@ class CommonConnection:
# <body> tag is the active event # <body> tag is the active event
if obj.chatstate and contact and contact.supports(nbxmpp.NS_CHATSTATES): if obj.chatstate and contact and contact.supports(nbxmpp.NS_CHATSTATES):
msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES) msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES)
only_chatste = False
if not obj.message: if not obj.message:
only_chatste = True
if only_chatste and not obj.session.enable_encryption:
msg_iq.setTag('no-store', msg_iq.setTag('no-store',
namespace=nbxmpp.NS_MSG_HINTS) namespace=nbxmpp.NS_MSG_HINTS)

View File

@ -830,8 +830,6 @@ class ConnectionHandlersBase:
self._nec_iq_error_received) self._nec_iq_error_received)
app.ged.register_event_handler('presence-received', ged.CORE, app.ged.register_event_handler('presence-received', ged.CORE,
self._nec_presence_received) self._nec_presence_received)
app.ged.register_event_handler('gc-presence-received', ged.CORE,
self._nec_gc_presence_received)
app.ged.register_event_handler('message-received', ged.CORE, app.ged.register_event_handler('message-received', ged.CORE,
self._nec_message_received) self._nec_message_received)
app.ged.register_event_handler('mam-message-received', ged.CORE, app.ged.register_event_handler('mam-message-received', ged.CORE,
@ -848,8 +846,6 @@ class ConnectionHandlersBase:
self._nec_iq_error_received) self._nec_iq_error_received)
app.ged.remove_event_handler('presence-received', ged.CORE, app.ged.remove_event_handler('presence-received', ged.CORE,
self._nec_presence_received) self._nec_presence_received)
app.ged.remove_event_handler('gc-presence-received', ged.CORE,
self._nec_gc_presence_received)
app.ged.remove_event_handler('message-received', ged.CORE, app.ged.remove_event_handler('message-received', ged.CORE,
self._nec_message_received) self._nec_message_received)
app.ged.remove_event_handler('mam-message-received', ged.CORE, app.ged.remove_event_handler('mam-message-received', ged.CORE,
@ -975,24 +971,6 @@ class ConnectionHandlersBase:
# resource signs off! # resource signs off!
self.stop_all_active_file_transfers(obj.contact) self.stop_all_active_file_transfers(obj.contact)
# disable encryption, since if any messages are
# lost they'll be not decryptable (note that
# this contradicts XEP-0201 - trying to get that
# in the XEP, though)
# there won't be any sessions here if the contact terminated
# their sessions before going offline (which we do)
for sess in self.get_sessions(jid):
sess_fjid = sess.jid.getStripped()
if sess.resource:
sess_fjid += '/' + sess.resource
if obj.fjid != sess_fjid:
continue
if sess.control:
sess.control.no_autonegotiation = False
if sess.enable_encryption:
sess.terminate_e2e()
if app.config.get('log_contact_status_changes') and \ if app.config.get('log_contact_status_changes') and \
app.config.should_log(self.name, obj.jid): app.config.should_log(self.name, obj.jid):
show = app.logger.convert_show_values_to_db_api_values(obj.show) show = app.logger.convert_show_values_to_db_api_values(obj.show)
@ -1004,15 +982,6 @@ class ConnectionHandlersBase:
message=obj.status, message=obj.status,
show=show) show=show)
def _nec_gc_presence_received(self, obj):
if obj.conn.name != self.name:
return
for sess in self.get_sessions(obj.fjid):
if obj.fjid != sess.jid:
continue
if sess.enable_encryption:
sess.terminate_e2e()
def _nec_message_received(self, obj): def _nec_message_received(self, obj):
if obj.conn.name != self.name: if obj.conn.name != self.name:
return return
@ -1210,14 +1179,7 @@ class ConnectionHandlersBase:
except KeyError: except KeyError:
return None return None
def terminate_sessions(self, send_termination=False): def terminate_sessions(self):
"""
Send termination messages and delete all active sessions
"""
for jid in self.sessions:
for thread_id in self.sessions[jid]:
self.sessions[jid][thread_id].terminate(send_termination)
self.sessions = {} self.sessions = {}
def delete_session(self, jid, thread_id): def delete_session(self, jid, thread_id):

View File

@ -2309,17 +2309,6 @@ class Oauth2CredentialsRequiredEvent(nec.NetworkIncomingEvent):
name = 'oauth2-credentials-required' name = 'oauth2-credentials-required'
base_network_events = [] base_network_events = []
class FailedDecryptEvent(nec.NetworkIncomingEvent):
name = 'failed-decrypt'
base_network_events = []
def generate(self):
self.conn = self.msg_obj.conn
self.fjid = self.msg_obj.fjid
self.timestamp = self.msg_obj.timestamp
self.session = self.msg_obj.session
return True
class SignedInEvent(nec.NetworkIncomingEvent): class SignedInEvent(nec.NetworkIncomingEvent):
name = 'signed-in' name = 'signed-in'
base_network_events = [] base_network_events = []

View File

@ -1,105 +0,0 @@
# common crypto functions (mostly specific to XEP-0116, but useful elsewhere)
# -*- coding:utf-8 -*-
## src/common/crypto.py
##
## Copyright (C) 2007 Brendan Taylor <whateley AT gmail.com>
##
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
import sys
import os
import math
from hashlib import sha256 as SHA256
# convert a large integer to a big-endian bitstring
def encode_mpi(n):
if n >= 256:
return encode_mpi(n // 256) + bytes([n % 256])
else:
return bytes([n])
# convert a large integer to a big-endian bitstring, padded with \x00s to
# a multiple of 16 bytes
def encode_mpi_with_padding(n):
return pad_to_multiple(encode_mpi(n), 16, '\x00', True)
# pad 'string' to a multiple of 'multiple_of' with 'char'.
# pad on the left if 'left', otherwise pad on the right.
def pad_to_multiple(string, multiple_of, char, left):
mod = len(string) % multiple_of
if mod == 0:
return string
else:
padding = (multiple_of - mod) * char
if left:
return padding + string
else:
return string + padding
# convert a big-endian bitstring to an integer
def decode_mpi(s):
if len(s) == 0:
return 0
else:
return 256 * decode_mpi(s[:-1]) + s[-1]
def sha256(string):
sh = SHA256()
sh.update(string)
return sh.digest()
base28_chr = "acdefghikmopqruvwxy123456789"
def sas_28x5(m_a, form_b):
sha = sha256(m_a + form_b + b'Short Authentication String')
lsb24 = decode_mpi(sha[-3:])
return base28(lsb24)
def base28(n):
if n >= 28:
return base28(n // 28) + base28_chr[n % 28]
else:
return base28_chr[n]
def random_bytes(bytes_):
return os.urandom(bytes_)
def generate_nonce():
return random_bytes(8)
# generate a random number between 'bottom' and 'top'
def srand(bottom, top):
# minimum number of bytes needed to represent that range
bytes = int(math.ceil(math.log(top - bottom, 256)))
# in retrospect, this is horribly inadequate.
return (decode_mpi(random_bytes(bytes)) % (top - bottom)) + bottom
# a faster version of (base ** exp) % mod
# taken from <http://lists.danga.com/pipermail/yadis/2005-September/001445.html>
def powmod(base, exp, mod):
square = base % mod
result = 1
while exp > 0:
if exp & 1: # exponent is odd
result = (result * square) % mod
square = (square * square) % mod
exp //= 2
return result

View File

@ -100,12 +100,6 @@ class NegotiationError(Exception):
""" """
pass pass
class DecryptionError(Exception):
"""
A message couldn't be decrypted into usable XML
"""
pass
class Cancelled(Exception): class Cancelled(Exception):
""" """
The user cancelled an operation The user cancelled an operation

View File

@ -1367,9 +1367,6 @@ def update_optional_features(account = None):
app.gajim_optional_features[a].append(nbxmpp.NS_CHATSTATES) app.gajim_optional_features[a].append(nbxmpp.NS_CHATSTATES)
if not app.config.get('ignore_incoming_xhtml'): if not app.config.get('ignore_incoming_xhtml'):
app.gajim_optional_features[a].append(nbxmpp.NS_XHTML_IM) app.gajim_optional_features[a].append(nbxmpp.NS_XHTML_IM)
if app.is_installed('PYCRYPTO') \
and app.config.get_per('accounts', a, 'enable_esessions'):
app.gajim_optional_features[a].append(nbxmpp.NS_ESESSION)
if app.config.get_per('accounts', a, 'answer_receipts'): if app.config.get_per('accounts', a, 'answer_receipts'):
app.gajim_optional_features[a].append(nbxmpp.NS_RECEIPTS) app.gajim_optional_features[a].append(nbxmpp.NS_RECEIPTS)
app.gajim_optional_features[a].append(nbxmpp.NS_JINGLE) app.gajim_optional_features[a].append(nbxmpp.NS_JINGLE)

File diff suppressed because it is too large Load Diff

View File

@ -69,10 +69,6 @@ class FeaturesWindow:
_('Ability to measure idle time, in order to set auto status.'), _('Ability to measure idle time, in order to set auto status.'),
_('Requires libxss library.'), _('Requires libxss library.'),
_('Requires python2.5.')), _('Requires python2.5.')),
_('End to End message encryption'): (self.pycrypto_available,
_('Encrypting chat messages.'),
_('Requires python-crypto.'),
_('Requires python-crypto.')),
_('RST Generator'): (self.docutils_available, _('RST Generator'): (self.docutils_available,
_('Generate XHTML output from RST code (see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html).'), _('Generate XHTML output from RST code (see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html).'),
_('Requires python-docutils.'), _('Requires python-docutils.'),
@ -166,9 +162,6 @@ class FeaturesWindow:
from gajim.common import idle from gajim.common import idle
return idle.Monitor.is_available() return idle.Monitor.is_available()
def pycrypto_available(self):
return app.is_installed('PYCRYPTO')
def docutils_available(self): def docutils_available(self):
try: try:
__import__('docutils') __import__('docutils')

View File

@ -200,13 +200,6 @@ class PrivateChatControl(ChatControl):
c = gc_c.as_contact() c = gc_c.as_contact()
self.gc_contact = gc_c self.gc_contact = gc_c
self.contact = c self.contact = c
if self.session:
# stop e2e
if self.session.enable_encryption:
thread_id = self.session.thread_id
self.session.terminate_e2e()
obj.conn.delete_session(obj.fjid, thread_id)
self.no_autonegotiation = False
self.draw_banner() self.draw_banner()
old_jid = obj.room_jid + '/' + obj.nick old_jid = obj.room_jid + '/' + obj.nick
new_jid = obj.room_jid + '/' + obj.new_nick new_jid = obj.room_jid + '/' + obj.new_nick
@ -282,17 +275,6 @@ class PrivateChatControl(ChatControl):
def update_contact(self): def update_contact(self):
self.contact = self.gc_contact.as_contact() self.contact = self.gc_contact.as_contact()
def begin_e2e_negotiation(self):
self.no_autonegotiation = True
if not self.session:
fjid = self.gc_contact.get_full_jid()
new_sess = app.connections[self.account].make_new_session(fjid,
type_=self.type_id)
self.set_session(new_sess)
self.session.negotiate_e2e(False)
def got_disconnected(self): def got_disconnected(self):
ChatControl.got_disconnected(self) ChatControl.got_disconnected(self)
@ -1859,20 +1841,6 @@ class GroupchatControl(ChatControlBase):
self.nick = obj.new_nick self.nick = obj.new_nick
self.new_nick = '' self.new_nick = ''
s = _('You are now known as %s') % self.nick s = _('You are now known as %s') % self.nick
# Stop all E2E sessions
nick_list = app.contacts.get_nick_list(self.account,
self.room_jid)
for nick_ in nick_list:
fjid_ = self.room_jid + '/' + nick_
ctrl = app.interface.msg_win_mgr.get_control(
fjid_, self.account)
if ctrl and ctrl.session and \
ctrl.session.enable_encryption:
thread_id = ctrl.session.thread_id
ctrl.session.terminate_e2e()
app.connections[self.account].delete_session(
fjid_, thread_id)
ctrl.no_autonegotiation = False
else: else:
s = _('%(nick)s is now known as %(new_nick)s') % { s = _('%(nick)s is now known as %(new_nick)s') % {
'nick': nick, 'new_nick': obj.new_nick} 'nick': nick, 'new_nick': obj.new_nick}
@ -2303,13 +2271,6 @@ class GroupchatControl(ChatControlBase):
contact.status = '' contact.status = ''
ctrl.update_ui() ctrl.update_ui()
ctrl.parent_win.redraw_tab(ctrl) ctrl.parent_win.redraw_tab(ctrl)
for sess in app.connections[self.account].get_sessions(fjid):
if sess.control:
sess.control.no_autonegotiation = False
if sess.enable_encryption:
sess.terminate_e2e()
app.connections[self.account].delete_session(fjid,
sess.thread_id)
# They can already be removed by the destroy function # They can already be removed by the destroy function
if self.room_jid in app.contacts.get_gc_list(self.account): if self.room_jid in app.contacts.get_gc_list(self.account):
app.contacts.remove_room(self.account, self.room_jid) app.contacts.remove_room(self.account, self.room_jid)

View File

@ -1184,12 +1184,6 @@ class Interface:
def handle_atom_entry(obj): def handle_atom_entry(obj):
AtomWindow.newAtomEntry(obj.atom_entry) AtomWindow.newAtomEntry(obj.atom_entry)
@staticmethod
def handle_event_failed_decrypt(obj):
details = _('Unable to decrypt message from %s\nIt may have been '
'tampered with.') % obj.fjid
dialogs.WarningDialog(_('Unable to decrypt message'), details)
def handle_event_zc_name_conflict(self, obj): def handle_event_zc_name_conflict(self, obj):
def on_ok(new_name): def on_ok(new_name):
app.config.set_per('accounts', obj.conn.name, 'name', new_name) app.config.set_per('accounts', obj.conn.name, 'name', new_name)
@ -1531,7 +1525,6 @@ class Interface:
'client-cert-passphrase': [ 'client-cert-passphrase': [
self.handle_event_client_cert_passphrase], self.handle_event_client_cert_passphrase],
'connection-lost': [self.handle_event_connection_lost], 'connection-lost': [self.handle_event_connection_lost],
'failed-decrypt': [(self.handle_event_failed_decrypt, ged.GUI2)],
'file-request-error': [self.handle_event_file_request_error], 'file-request-error': [self.handle_event_file_request_error],
'file-request-received': [self.handle_event_file_request], 'file-request-received': [self.handle_event_file_request],
'gc-invitation-received': [self.handle_event_gc_invitation], 'gc-invitation-received': [self.handle_event_gc_invitation],

View File

@ -32,7 +32,6 @@ import uuid
from gajim.common import app from gajim.common import app
from gajim.common import helpers from gajim.common import helpers
from gajim.common import ged from gajim.common import ged
from gajim.common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession
# Derived types MUST register their type IDs here if custom behavor is required # Derived types MUST register their type IDs here if custom behavor is required
TYPE_CHAT = 'chat' TYPE_CHAT = 'chat'
@ -200,19 +199,6 @@ class MessageControl(object):
if session and oldsession: if session and oldsession:
oldsession.control = None oldsession.control = None
crypto_changed = bool(session and isinstance(session,
EncryptedStanzaSession) and session.enable_encryption) != \
bool(oldsession and isinstance(oldsession, EncryptedStanzaSession) \
and oldsession.enable_encryption)
archiving_changed = bool(session and isinstance(session,
ArchivingStanzaSession) and session.archiving) != \
bool(oldsession and isinstance(oldsession,
ArchivingStanzaSession) and oldsession.archiving)
if crypto_changed or archiving_changed:
self.print_session_details(oldsession)
def remove_session(self, session): def remove_session(self, session):
if session != self.session: if session != self.session:
return return

View File

@ -1,128 +0,0 @@
# -*- coding:utf-8 -*-
## src/secrets.py
##
## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
##
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
from gajim.common import configpaths
import Crypto
from gajim.common import crypto
from gajim.common import exceptions
import os
import pickle
secrets_filename = configpaths.get('SECRETS_FILE')
secrets_cache = None
class Secrets():
def __init__(self, filename):
self.filename = filename
self.srs = {}
self.pubkeys = {}
self.privkeys = {}
def cancel(self):
raise exceptions.Cancelled
def save(self):
f = open(secrets_filename, 'wb')
pickle.dump(self, f, protocol=2)
f.close()
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 [(x, y) for x, y in our_secrets if x == srs][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(2048, crypto.random_bytes)
self.privkeys[account] = pk
self.save()
return pk
def load_secrets(filename):
f = open(filename, 'rb')
try:
secrets = pickle.load(f, encoding='latin1')
# We do that to be able to read files written in py2
for acct in secrets.srs:
for jid in secrets.srs[acct]:
for (secret, verified) in list(secrets.srs[acct][jid]):
if type(secret) is str:
secrets.srs[acct][jid].remove((secret, verified))
secrets.srs[acct][jid].append((secret.encode('latin1'), verified))
except (KeyError, EOFError, ImportError):
f.close()
secrets = Secrets(filename)
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

View File

@ -21,11 +21,13 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>. ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
## ##
import string
import random
import itertools
from gajim.common import helpers from gajim.common import helpers
from gajim.common import events from gajim.common import events
from gajim.common import exceptions
from gajim.common import app from gajim.common import app
from gajim.common import stanza_session
from gajim.common import contacts from gajim.common import contacts
from gajim.common import ged from gajim.common import ged
from gajim.common.connection_handlers_events import ChatstateReceivedEvent, \ from gajim.common.connection_handlers_events import ChatstateReceivedEvent, \
@ -34,28 +36,48 @@ from gajim.common.const import KindConstant
from gajim import message_control from gajim import message_control
from gajim import notify from gajim import notify
from gajim import dialogs from gajim import dialogs
from gajim import negotiation
class ChatControlSession(stanza_session.EncryptedStanzaSession):
class ChatControlSession(object):
def __init__(self, conn, jid, thread_id, type_='chat'): def __init__(self, conn, jid, thread_id, type_='chat'):
stanza_session.EncryptedStanzaSession.__init__(self, conn, jid, thread_id, self.conn = conn
type_='chat') self.jid = jid
app.ged.register_event_handler('decrypted-message-received', ged.PREGUI, self.type_ = type_
self._nec_decrypted_message_received) self.resource = jid.getResource()
self.control = None self.control = None
def detach_from_control(self): if thread_id:
if self.control: self.received_thread_id = True
self.control.set_session(None) self.thread_id = thread_id
else:
self.received_thread_id = False
if type_ == 'normal':
self.thread_id = None
else:
self.thread_id = self.generate_thread_id()
def acknowledge_termination(self): self.loggable = True
self.detach_from_control()
stanza_session.EncryptedStanzaSession.acknowledge_termination(self)
def terminate(self, send_termination = True): self.last_send = 0
stanza_session.EncryptedStanzaSession.terminate(self, send_termination) self.last_receive = 0
self.detach_from_control()
app.ged.register_event_handler('decrypted-message-received',
ged.PREGUI,
self._nec_decrypted_message_received)
def generate_thread_id(self):
return ''.join(
[f(string.ascii_letters) for f in itertools.repeat(
random.choice, 32)]
)
def is_loggable(self):
return app.config.should_log(self.conn.name,
self.jid.getStripped())
def get_to(self):
to = str(self.jid)
return app.get_jid_without_resource(to) + '/' + self.resource
def _nec_decrypted_message_received(self, obj): def _nec_decrypted_message_received(self, obj):
""" """
@ -400,177 +422,3 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
else: else:
bb_jid, bb_account = jid, self.conn.name bb_jid, bb_account = jid, self.conn.name
app.interface.roster.select_contact(bb_jid, bb_account) app.interface.roster.select_contact(bb_jid, bb_account)
# ---- ESessions stuff ---
def handle_negotiation(self, form):
if form.getField('accept') and not form['accept'] in ('1', 'true'):
self.cancelled_negotiation()
return
# encrypted session states. these are described in stanza_session.py
try:
if form.getType() == 'form' and 'security' in form.asDict():
security_options = [x[1] for x in form.getField('security').\
getOptions()]
if security_options == ['none']:
self.respond_archiving(form)
else:
# bob responds
# we don't support 3-message negotiation as the responder
if 'dhkeys' in form.asDict():
self.fail_bad_negotiation('3 message negotiation not '
'supported when responding', ('dhkeys',))
return
negotiated, not_acceptable, ask_user = \
self.verify_options_bob(form)
if ask_user:
def accept_nondefault_options(is_checked):
self.dialog.destroy()
negotiated.update(ask_user)
self.respond_e2e_bob(form, negotiated,
not_acceptable)
def reject_nondefault_options():
self.dialog.destroy()
for key in ask_user.keys():
not_acceptable.append(key)
self.respond_e2e_bob(form, negotiated,
not_acceptable)
self.dialog = dialogs.YesNoDialog(_('Confirm these '
'session options'),
_('The remote client wants to negotiate a session '
'with these features:\n\n%s\n\nAre these options '
'acceptable?''') % (
negotiation.describe_features(ask_user)),
on_response_yes=accept_nondefault_options,
on_response_no=reject_nondefault_options,
transient_for=self.control.parent_win.window)
else:
self.respond_e2e_bob(form, negotiated, not_acceptable)
return
elif self.status == 'requested-archiving' and form.getType() == \
'submit':
try:
self.archiving_accepted(form)
except exceptions.NegotiationError as details:
self.fail_bad_negotiation(details)
return
# alice accepts
elif self.status == 'requested-e2e' and form.getType() == 'submit':
negotiated, not_acceptable, ask_user = self.verify_options_alice(
form)
if ask_user:
def accept_nondefault_options(is_checked):
if dialog:
dialog.destroy()
if is_checked:
allow_no_log_for = app.config.get_per(
'accounts', self.conn.name,
'allow_no_log_for').split()
jid = str(self.jid)
if jid not in allow_no_log_for:
allow_no_log_for.append(jid)
app.config.set_per('accounts', self.conn.name,
'allow_no_log_for', ' '.join(allow_no_log_for))
negotiated.update(ask_user)
try:
self.accept_e2e_alice(form, negotiated)
except exceptions.NegotiationError as details:
self.fail_bad_negotiation(details)
def reject_nondefault_options():
self.reject_negotiation()
dialog.destroy()
allow_no_log_for = app.config.get_per('accounts',
self.conn.name, 'allow_no_log_for').split()
if str(self.jid) in allow_no_log_for:
dialog = None
accept_nondefault_options(False)
else:
dialog = dialogs.YesNoDialog(_('Confirm these session '
'options'),
_('The remote client selected these options:\n\n%s'
'\n\nContinue with the session?') % (
negotiation.describe_features(ask_user)),
_('Always accept for this contact'),
on_response_yes = accept_nondefault_options,
on_response_no = reject_nondefault_options,
transient_for=self.control.parent_win.window)
else:
try:
self.accept_e2e_alice(form, negotiated)
except exceptions.NegotiationError as details:
self.fail_bad_negotiation(details)
return
elif self.status == 'responded-archiving' and form.getType() == \
'result':
try:
self.we_accept_archiving(form)
except exceptions.NegotiationError as details:
self.fail_bad_negotiation(details)
return
elif self.status == 'responded-e2e' and form.getType() == 'result':
try:
self.accept_e2e_bob(form)
except exceptions.NegotiationError as details:
self.fail_bad_negotiation(details)
return
elif self.status == 'identified-alice' and form.getType() == 'result':
try:
self.final_steps_alice(form)
except exceptions.NegotiationError as details:
self.fail_bad_negotiation(details)
return
except exceptions.Cancelled:
# user cancelled the negotiation
self.reject_negotiation()
return
if form.getField('terminate') and\
form.getField('terminate').getValue() in ('1', 'true'):
self.acknowledge_termination()
self.conn.delete_session(str(self.jid), self.thread_id)
return
# non-esession negotiation. this isn't very useful, but i'm keeping it
# around to test my test suite.
if form.getType() == 'form':
if not self.control:
jid, resource = app.get_room_and_nick_from_fjid(str(self.jid))
account = self.conn.name
contact = app.contacts.get_contact(account, str(self.jid),
resource)
if not contact:
contact = app.contacts.create_contact(jid=jid, account=account,
resource=resource, show=self.conn.get_status())
app.interface.new_chat(contact, account, resource=resource,
session=self)
negotiation.FeatureNegotiationWindow(account, str(self.jid), self,
form)

View File

@ -212,25 +212,6 @@
} }
] ]
}, },
/* ESession support */
{
"name": "python3-pycrypto",
"buildsystem": "simple",
"build-commands": [
"python3 setup.py install --prefix=/app"
],
"sources": [
{
"type": "archive",
"url": "https://pypi.python.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz",
"sha256": "f2ce1e989b272cfcb677616763e0a2e7ec659effa67a88aa92b3a65528f60a3c"
},
{
"type": "patch",
"path": "flatpak/CVE-2013-7459.patch"
}
]
},
/* gnupg support */ /* gnupg support */
{ {
"name": "python3-python-gnupg", "name": "python3-python-gnupg",

View File

@ -57,8 +57,6 @@ configpaths.init()
configpaths.override_path('DATA', gajim_root + '/gajim/data') configpaths.override_path('DATA', gajim_root + '/gajim/data')
from common.stanza_session import StanzaSession
# name to use for the test account # name to use for the test account
account_name = 'test' account_name = 'test'

View File

@ -16,7 +16,6 @@ from gajim.common.connection_handlers_events import MessageReceivedEvent
from gajim.common.connection_handlers_events import DecryptedMessageReceivedEvent from gajim.common.connection_handlers_events import DecryptedMessageReceivedEvent
import nbxmpp import nbxmpp
from gajim.common.stanza_session import StanzaSession
from gajim.session import ChatControlSession from gajim.session import ChatControlSession
from gajim.roster_window import RosterWindow from gajim.roster_window import RosterWindow
@ -30,59 +29,6 @@ app.interface = MockInterface()
# name to use for the test account # name to use for the test account
account_name = account1 account_name = account1
class TestStanzaSession(unittest.TestCase):
''' Testclass for common/stanzasession.py '''
def setUp(self):
self.jid = nbxmpp.JID('test@example.org/Gajim')
self.conn = MockConnection(account_name, {'send_stanza': None})
self.sess = StanzaSession(self.conn, self.jid, None, 'chat')
def test_generate_thread_id(self):
# thread_id is a string
self.assertTrue(isinstance(self.sess.thread_id, str))
# it should be somewhat long, to avoid clashes
self.assertTrue(len(self.sess.thread_id) >= 32)
def test_is_loggable(self):
# by default a session should be loggable
# (unless the no_log_for setting says otherwise)
self.assertTrue(self.sess.is_loggable())
def test_terminate(self):
# termination is sent by default
self.sess.last_send = time.time()
self.sess.terminate()
self.assertEqual(None, self.sess.status)
calls = self.conn.mockGetNamedCalls('send_stanza')
msg = calls[0].getParam(0)
self.assertEqual(msg.getThread(), self.sess.thread_id)
def test_terminate_without_sending(self):
# no termination is sent if no messages have been sent in the session
self.sess.terminate()
self.assertEqual(None, self.sess.status)
calls = self.conn.mockGetNamedCalls('send_stanza')
self.assertEqual(0, len(calls))
def test_terminate_no_remote_xep_201(self):
# no termination is sent if only messages without thread ids have been
# received
self.sess.last_send = time.time()
self.sess.last_receive = time.time()
self.sess.terminate()
self.assertEqual(None, self.sess.status)
calls = self.conn.mockGetNamedCalls('send_stanza')
self.assertEqual(0, len(calls))
class TestChatControlSession(unittest.TestCase): class TestChatControlSession(unittest.TestCase):
''' Testclass for session.py ''' ''' Testclass for session.py '''

View File

@ -91,7 +91,6 @@ function install_deps {
PIP_REQUIREMENTS="\ PIP_REQUIREMENTS="\
git+https://dev.gajim.org/gajim/python-nbxmpp.git git+https://dev.gajim.org/gajim/python-nbxmpp.git
git+https://github.com/dlitz/pycrypto.git
git+https://dev.gajim.org/lovetox/pybonjour-python3.git git+https://dev.gajim.org/lovetox/pybonjour-python3.git
keyring keyring
python-gnupg python-gnupg

View File

@ -23,7 +23,6 @@ certifi
git+https://dev.gajim.org/gajim/python-nbxmpp.git git+https://dev.gajim.org/gajim/python-nbxmpp.git
git+https://dev.gajim.org/lovetox/pybonjour-python3.git git+https://dev.gajim.org/lovetox/pybonjour-python3.git
protobuf protobuf
git+https://github.com/dlitz/pycrypto.git
cryptography cryptography
pyopenssl pyopenssl
python-gnupg python-gnupg