Commit 32b74b45 authored by Philipp Hörist's avatar Philipp Hörist

Remove support for ESessions

Fixes #5294
parent 56fbe32b
......@@ -15,7 +15,6 @@
- python3-keyring for saving your password to your system keyring
- python3-pil (pillow) for support of webp avatars
- python3-crypto to enable End to end encryption
- python3-gnupg to enable GPG encryption
- For zeroconf (bonjour) you need python3-dbus
- gir1.2-gspell-1 and hunspell-LANG where lang is your locale eg. en, fr etc
......
......@@ -42,7 +42,6 @@ from gajim.common import app
from gajim.common import helpers
from gajim.common import ged
from gajim.common import i18n
from gajim.common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession
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_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO
......@@ -207,9 +206,6 @@ class ChatControl(ChatControlBase):
session.control = self
self.session = session
if session.enable_encryption:
self.print_esession_details()
# Enable encryption if needed
self.no_autonegotiation = False
self.add_actions()
......@@ -231,8 +227,6 @@ class ChatControl(ChatControlBase):
# Dont connect this when PrivateChatControl is used
app.ged.register_event_handler('update-roster-avatar', ged.GUI1,
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,
self._nec_chatstate_received)
app.ged.register_event_handler('caps-received', ged.GUI1,
......@@ -920,53 +914,6 @@ class ChatControl(ChatControlBase):
chatstate=chatstate_to_send, xhtml=xhtml,
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):
return app.nicks[self.account]
......@@ -1186,8 +1133,6 @@ class ChatControl(ChatControlBase):
if self.TYPE_ID == message_control.TYPE_CHAT:
app.ged.remove_event_handler('update-roster-avatar', ged.GUI1,
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,
self._nec_chatstate_received)
app.ged.remove_event_handler('caps-received', ged.GUI1,
......@@ -1482,57 +1427,6 @@ class ChatControl(ChatControlBase):
"""
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):
ChatControlBase.got_connected(self)
# Refreshing contact
......
......@@ -451,9 +451,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
plugin = app.plugin_manager.encryption_plugins[encryption]
if not plugin.activate_encryption(self):
return
else:
if not self.widget_name == 'groupchat_control':
self.terminate_esessions()
action.set_state(param)
self.set_encryption_state(encryption)
self.set_encryption_menu_icon()
......
......@@ -147,7 +147,6 @@ caps_hash = {}
_dependencies = {
'PYTHON-DBUS': False,
'PYBONJOUR': False,
'PYCRYPTO': False,
'PYGPG': False,
'GPG_BINARY': False,
'FARSTREAM': False,
......@@ -188,13 +187,6 @@ def detect_dependencies():
except Exception:
pass
# PYCRYPTO
try:
import Crypto
_dependencies['PYCRYPTO'] = True
except ImportError:
pass
# python-gnupg
try:
import gnupg
......
......@@ -338,8 +338,6 @@ class Config:
'keyid': [ opt_str, '', '', True ],
'gpg_sign_presence': [ opt_bool, True, _('If disabled, don\'t sign presences with GPG key, even if GPG is configured.') ],
'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')],
'tls_version': [ opt_str, '1.2', '' ],
'cipher_list': [ opt_str, 'HIGH:!aNULL:RC4-SHA', '' ],
......
......@@ -340,10 +340,7 @@ class CommonConnection:
# <body> tag is the active event
if obj.chatstate and contact and contact.supports(nbxmpp.NS_CHATSTATES):
msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES)
only_chatste = False
if not obj.message:
only_chatste = True
if only_chatste and not obj.session.enable_encryption:
msg_iq.setTag('no-store',
namespace=nbxmpp.NS_MSG_HINTS)
......
......@@ -830,8 +830,6 @@ class ConnectionHandlersBase:
self._nec_iq_error_received)
app.ged.register_event_handler('presence-received', ged.CORE,
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,
self._nec_message_received)
app.ged.register_event_handler('mam-message-received', ged.CORE,
......@@ -848,8 +846,6 @@ class ConnectionHandlersBase:
self._nec_iq_error_received)
app.ged.remove_event_handler('presence-received', ged.CORE,
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,
self._nec_message_received)
app.ged.remove_event_handler('mam-message-received', ged.CORE,
......@@ -975,24 +971,6 @@ class ConnectionHandlersBase:
# resource signs off!
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 \
app.config.should_log(self.name, obj.jid):
show = app.logger.convert_show_values_to_db_api_values(obj.show)
......@@ -1004,15 +982,6 @@ class ConnectionHandlersBase:
message=obj.status,
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):
if obj.conn.name != self.name:
return
......@@ -1210,14 +1179,7 @@ class ConnectionHandlersBase:
except KeyError:
return None
def terminate_sessions(self, send_termination=False):
"""
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)
def terminate_sessions(self):
self.sessions = {}
def delete_session(self, jid, thread_id):
......
......@@ -2309,17 +2309,6 @@ class Oauth2CredentialsRequiredEvent(nec.NetworkIncomingEvent):
name = 'oauth2-credentials-required'
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):
name = 'signed-in'
base_network_events = []
......
# 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
......@@ -100,12 +100,6 @@ class NegotiationError(Exception):
"""
pass
class DecryptionError(Exception):
"""
A message couldn't be decrypted into usable XML
"""
pass
class Cancelled(Exception):
"""
The user cancelled an operation
......
......@@ -1367,9 +1367,6 @@ def update_optional_features(account = None):
app.gajim_optional_features[a].append(nbxmpp.NS_CHATSTATES)
if not app.config.get('ignore_incoming_xhtml'):
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'):
app.gajim_optional_features[a].append(nbxmpp.NS_RECEIPTS)
app.gajim_optional_features[a].append(nbxmpp.NS_JINGLE)
......
This diff is collapsed.
......@@ -69,10 +69,6 @@ class FeaturesWindow:
_('Ability to measure idle time, in order to set auto status.'),
_('Requires libxss library.'),
_('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,
_('Generate XHTML output from RST code (see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html).'),
_('Requires python-docutils.'),
......@@ -166,9 +162,6 @@ class FeaturesWindow:
from gajim.common import idle
return idle.Monitor.is_available()
def pycrypto_available(self):
return app.is_installed('PYCRYPTO')
def docutils_available(self):
try:
__import__('docutils')
......
......@@ -200,13 +200,6 @@ class PrivateChatControl(ChatControl):
c = gc_c.as_contact()
self.gc_contact = gc_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()
old_jid = obj.room_jid + '/' + obj.nick
new_jid = obj.room_jid + '/' + obj.new_nick
......@@ -282,17 +275,6 @@ class PrivateChatControl(ChatControl):
def update_contact(self):
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):
ChatControl.got_disconnected(self)
......@@ -1859,20 +1841,6 @@ class GroupchatControl(ChatControlBase):
self.nick = obj.new_nick
self.new_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:
s = _('%(nick)s is now known as %(new_nick)s') % {
'nick': nick, 'new_nick': obj.new_nick}
......@@ -2303,13 +2271,6 @@ class GroupchatControl(ChatControlBase):
contact.status = ''
ctrl.update_ui()
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
if self.room_jid in app.contacts.get_gc_list(self.account):
app.contacts.remove_room(self.account, self.room_jid)
......
......@@ -1184,12 +1184,6 @@ class Interface:
def handle_atom_entry(obj):
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 on_ok(new_name):
app.config.set_per('accounts', obj.conn.name, 'name', new_name)
......@@ -1531,7 +1525,6 @@ class Interface:
'client-cert-passphrase': [
self.handle_event_client_cert_passphrase],
'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-received': [self.handle_event_file_request],
'gc-invitation-received': [self.handle_event_gc_invitation],
......
......@@ -32,7 +32,6 @@ import uuid
from gajim.common import app
from gajim.common import helpers
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
TYPE_CHAT = 'chat'
......@@ -200,19 +199,6 @@ class MessageControl(object):
if session and oldsession:
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):
if session != self.session:
return
......
# -*- 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
This diff is collapsed.
......@@ -212,25 +212,6 @@
}
]
},
/* ESession support */