From 6fdd7c0f88aa153f63adf89cd4809230212c9018 Mon Sep 17 00:00:00 2001 From: Stephan Erb <steve-e@h3c.de> Date: Sat, 29 Dec 2007 23:28:27 +0000 Subject: [PATCH] Rework GPG behaviour: Only encrypt when the receiver is trusted. You have to sign its key or it has to be signed by someone you trust. Fixes #109 Make checkbox insensitive when GPG is disabled on an account (or no passphrase given). Auto assign trusted keys on received presence. Deny encryption on missmatch of assigned key and signing key. Fixes #3376 Do not disable encrypted when receiving an unencrypted message. Print whether a received message was encrypted or not. TODO: Remove togglebutton, its useless now --- src/chat_control.py | 113 ++++++++++++++---------------- src/common/GnuPG.py | 4 +- src/common/connection.py | 10 ++- src/common/connection_handlers.py | 1 + src/common/helpers.py | 33 +++++++++ src/gajim.py | 9 +-- src/roster_window.py | 12 ++-- 7 files changed, 109 insertions(+), 73 deletions(-) diff --git a/src/chat_control.py b/src/chat_control.py index 6459e4fa15..5d773ca3ac 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -1050,15 +1050,17 @@ class ChatControl(ChatControlBase): self.on_avatar_eventbox_button_press_event) self.handlers[id] = widget - widget = self.xml.get_widget('gpg_togglebutton') - id = widget.connect('clicked', self.on_toggle_gpg_togglebutton) - self.handlers[id] = widget - - if self.contact.jid in gajim.encrypted_chats[self.account]: - self.xml.get_widget('gpg_togglebutton').set_active(True) - self.set_session(session) + # Enable ecryption if needed + gpg_pref = gajim.config.get_per('contacts', contact.jid, + 'gpg_enabled') + if gpg_pref and gajim.config.get_per('accounts', self.account, 'keyid') and\ + gajim.connections[self.account].USE_GPG: + gajim.encrypted_chats[self.account].append(contact.jid) + msg = _('GPG encryption enabled') + ChatControlBase.print_conversation_line(self, msg, 'status', '', None) + self.status_tooltip = gtk.Tooltips() self.update_ui() # restore previous conversation @@ -1164,8 +1166,6 @@ class ChatControl(ChatControlBase): gtk.gdk.INTERP_BILINEAR) banner_status_img.set_from_pixbuf(scaled_pix) - self._update_gpg() - def draw_banner_text(self): '''Draw the text in the fat line at the top of the window that houses the name, jid. @@ -1254,34 +1254,26 @@ class ChatControl(ChatControlBase): # setup the label that holds name and jid banner_name_label.set_markup(label_text) - def on_toggle_gpg_togglebutton(self, widget): - gajim.config.set_per('contacts', self.contact.jid, 'gpg_enabled', - widget.get_active()) - - def _update_gpg(self): - tb = self.xml.get_widget('gpg_togglebutton') - # we can do gpg - # if self.contact is our own contact info (transports), - # don't enable pgp - if self.contact.keyID and not gajim.jid_is_transport(self.contact.jid): - tb.set_sensitive(True) - tt = _('OpenPGP Encryption') - - # restore gpg pref - gpg_pref = gajim.config.get_per('contacts', self.contact.jid, - 'gpg_enabled') - if gpg_pref == None: - gajim.config.add_per('contacts', self.contact.jid) - gpg_pref = gajim.config.get_per('contacts', self.contact.jid, - 'gpg_enabled') - tb.set_active(gpg_pref) - + def _toggle_gpg(self): + ec = gajim.encrypted_chats[self.account] + if self.contact.jid in ec: + # Disable encryption + ec.remove(self.contact.jid) + gpg_is_active = False + msg = _('GPG encryption disabled') else: - tb.set_sensitive(False) - #we talk about a contact here - tt = _('%s has not broadcast an OpenPGP key, nor has one been assigned') %\ - self.contact.get_shown_name() - gtk.Tooltips().set_tip(self.xml.get_widget('gpg_eventbox'), tt) + # Enable encryption + ec.append(self.contact.jid) + gpg_is_active = True + msg = _('GPG encryption enabled') + + gpg_pref = gajim.config.get_per('contacts', self.contact.jid, + 'gpg_enabled') + if gpg_pref is None: + gajim.config.add_per('contacts', self.contact.jid) + gajim.config.set_per('contacts', self.contact.jid, 'gpg_enabled', + gpg_is_active) + ChatControlBase.print_conversation_line(self, msg, 'status', '', None) def _process_command(self, message): if message[0] != '/': @@ -1369,9 +1361,13 @@ class ChatControl(ChatControlBase): encrypted = bool(self.session) and self.session.enable_encryption keyID = '' - if self.xml.get_widget('gpg_togglebutton').get_active(): + gpg_pref = gajim.config.get_per('contacts', contact.jid, + 'gpg_enabled') + if gpg_pref: keyID = contact.keyID encrypted = True + if keyID == '': + keyID = 'UNKNOWN' chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \ 'disabled' @@ -1401,7 +1397,7 @@ class ChatControl(ChatControlBase): gobject.source_remove(self.possible_paused_timeout_id) gobject.source_remove(self.possible_inactive_timeout_id) self._schedule_activity_timers() - + if not ChatControlBase.send_message(self, message, keyID, type = 'chat', chatstate = chatstate_to_send, composing_xep = composing_xep, process_command = process_command): @@ -1512,17 +1508,14 @@ class ChatControl(ChatControlBase): # GPG encryption ec = gajim.encrypted_chats[self.account] if encrypted and jid not in ec: - msg = _('OpenPGP Encryption enabled') + msg = _('The following message was encrypted') ChatControlBase.print_conversation_line(self, msg, 'status', '', tim) - ec.append(jid) + self._toggle_gpg() elif not encrypted and jid in ec: - msg = _('OpenPGP Encryption disabled') + msg = _('The following message was NOT encrypted') ChatControlBase.print_conversation_line(self, msg, 'status', '', tim) - ec.remove(jid) - self.xml.get_widget('gpg_togglebutton').set_active(encrypted) - if not frm: kind = 'incoming' name = contact.get_shown_name() @@ -1649,13 +1642,17 @@ class ChatControl(ChatControlBase): contact = self.parent_win.get_active_contact() jid = contact.jid - - # check if gpg capabitlies or else make gpg toggle insensitive - gpg_btn = self.xml.get_widget('gpg_togglebutton') - isactive = gpg_btn.get_active() - is_sensitive = gpg_btn.get_property('sensitive') - toggle_gpg_menuitem.set_active(isactive) - toggle_gpg_menuitem.set_property('sensitive', is_sensitive) + + # check if we support and use gpg + if not gajim.config.get_per('accounts', self.account, 'keyid') or\ + not gajim.connections[self.account].USE_GPG or\ + gajim.jid_is_transport(jid): + toggle_gpg_menuitem.set_sensitive(False) + else: + toggle_gpg_menuitem.set_sensitive(True) + gpg_pref = gajim.config.get_per('contacts', jid, + 'gpg_enabled') + toggle_gpg_menuitem.set_active(bool(gpg_pref)) # TODO: check that the remote client supports e2e if not gajim.HAVE_PYCRYPTO: @@ -1694,14 +1691,15 @@ class ChatControl(ChatControlBase): id = send_file_menuitem.connect('activate', self._on_send_file_menuitem_activate) self.handlers[id] = send_file_menuitem - id = add_to_roster_menuitem.connect('activate', + id = add_to_roster_menuitem.connect('activate', self._on_add_to_roster_menuitem_activate) - self.handlers[id] = add_to_roster_menuitem - id = toggle_gpg_menuitem.connect('activate', + self.handlers[id] = add_to_roster_menuitem + id = toggle_gpg_menuitem.connect('activate', self._on_toggle_gpg_menuitem_activate) + self.handlers[id] = toggle_gpg_menuitem id = toggle_e2e_menuitem.connect('activate', self._on_toggle_e2e_menuitem_activate) - self.handlers[id] = toggle_gpg_menuitem + self.handlers[id] = toggle_e2e_menuitem id = information_menuitem.connect('activate', self._on_contact_information_menuitem_activate) self.handlers[id] = information_menuitem @@ -2154,10 +2152,7 @@ class ChatControl(ChatControlBase): gajim.interface.roster.on_info(widget, self.contact, self.account) def _on_toggle_gpg_menuitem_activate(self, widget): - # update the button - # this is reverse logic, as we are on 'activate' (before change happens) - tb = self.xml.get_widget('gpg_togglebutton') - tb.set_active(not tb.get_active()) + self._toggle_gpg() def _on_convert_to_gc_menuitem_activate(self, widget): '''user want to invite some friends to chat''' diff --git a/src/common/GnuPG.py b/src/common/GnuPG.py index c7603b8b42..4e281c71db 100644 --- a/src/common/GnuPG.py +++ b/src/common/GnuPG.py @@ -45,8 +45,6 @@ if gajim.HAVE_GPG: self.options.armor = 1 self.options.meta_interactive = 0 self.options.extra_args.append('--no-secmem-warning') - # Nolith's patch - prevent crashs on non fully-trusted keys - self.options.extra_args.append('--always-trust') if self.use_agent: self.options.extra_args.append('--use-agent') @@ -173,7 +171,7 @@ if gajim.HAVE_GPG: try: proc.wait() except IOError: pass - + keyid = '' if resp.has_key('GOODSIG'): keyid = resp['GOODSIG'].split()[0] diff --git a/src/common/connection.py b/src/common/connection.py index 0f8441e17d..7147f290b6 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -961,9 +961,15 @@ class Connection(ConnectionHandlers): fjid += '/' + resource msgtxt = msg msgenc = '' + if keyID and self.USE_GPG: - #encrypt - msgenc, error = self.gpg.encrypt(msg, [keyID]) + if keyID == 'UNKNOWN': + error = _('Neither the remote presence is signed, nor a key was assigned.') + elif keyID[8:] == 'MISMATCH': + error = _('The contact\'s key does not match the key assigned in Gajim.') + else: + #encrypt + msgenc, error = self.gpg.encrypt(msg, [keyID]) if msgenc and not error: msgtxt = '[This message is encrypted]' lang = os.getenv('LANG') diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index 33af3e6707..375279b512 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -2199,6 +2199,7 @@ returns the session that we last sent a message to.''' self.dispatch('ERROR', (_('OpenPGP passphrase was not given'), #%s is the account name here _('You will be connected to %s without OpenPGP.') % self.name)) + self.USE_GPG = False signed = '' self.connected = STATUS_LIST.index(show) sshow = helpers.get_xmpp_show(show) diff --git a/src/common/helpers.py b/src/common/helpers.py index 7e6d96d9bc..3ac741226b 100644 --- a/src/common/helpers.py +++ b/src/common/helpers.py @@ -1094,3 +1094,36 @@ def get_transport_path(transport): return os.path.join(gajim.MY_ICONSETS_PATH, 'transports', transport) # No transport folder found, use default jabber one return get_iconset_path(gajim.config.get('iconset')) + +def prepare_and_validate_gpg_keyID(account, jid, keyID): + '''Returns an eight char long keyID that can be used with for GPG encryption with this contact. + If the given keyID is None, return UNKNOWN; if the key does not match the assigned key + XXXXXXXXMISMATCH is returned. If the key is trusted and not yet assigned, assign it''' + if gajim.connections[account].USE_GPG: + if len(keyID) == 16: + keyID = keyID[8:] + + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + + if jid in attached_keys and keyID: + attachedkeyID = attached_keys[attached_keys.index(jid) + 1] + if attachedkeyID != keyID: + # Mismatch! Another gpg key was expected + keyID += 'MISMATCH' + elif jid in attached_keys: + # An unsigned presence, just use the assigned key + keyID = attached_keys[attached_keys.index(jid) + 1] + elif keyID: + public_keys = gajim.connections[account].ask_gpg_keys() + # Assign the corresponding key, if we have it in our keyring + if public_keys.has_key(keyID): + for u in gajim.contacts.get_contacts(account, jid): + u.keyID = keyID + keys_str = gajim.config.get_per('accounts', account, 'attached_gpg_keys') + keys_str += jid + ' ' + keyID + ' ' + gajim.config.set_per('accounts', account, 'attached_gpg_keys', keys_str) + elif keyID is None: + keyID = 'UNKNOWN' + return keyID + diff --git a/src/gajim.py b/src/gajim.py index b6cb3b6ab5..dd5f4f3474 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -579,10 +579,11 @@ class Interface: jid = array[0].split('/')[0] keyID = array[5] contact_nickname = array[7] - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - if jid in attached_keys: - keyID = attached_keys[attached_keys.index(jid) + 1] + + # Get the proper keyID + keyID = helpers.prepare_and_validate_gpg_keyID(account, + jid, keyID) + resource = array[3] if not resource: resource = '' diff --git a/src/roster_window.py b/src/roster_window.py index 7058022a93..98d0693a5d 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -1912,12 +1912,11 @@ class RosterWindow: if keyID[0] == _('None'): if contact.jid in keys: del keys[contact.jid] - for u in gajim.contacts.get_contacts(account, contact.jid): - u.keyID = '' + keyID = '' else: - keys[contact.jid] = keyID[0] - for u in gajim.contacts.get_contacts(account, contact.jid): - u.keyID = keyID[0] + keyID = keyID[0] + keys[contact.jid] = keyID + if gajim.interface.msg_win_mgr.has_window(contact.jid, account): ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account) ctrl.update_ui() @@ -1925,6 +1924,9 @@ class RosterWindow: for jid in keys: keys_str += jid + ' ' + keys[jid] + ' ' gajim.config.set_per('accounts', account, 'attached_gpg_keys', keys_str) + for u in gajim.contacts.get_contacts(account, contact.jid): + u.keyID = helpers.prepare_and_validate_gpg_keyID(account, + contact.jid, keyID) def update_avatar_in_gui(self, jid, account): # Update roster -- GitLab