From 40c6835600cc6ebcb816f9aee4a6540ef8e362f0 Mon Sep 17 00:00:00 2001 From: Samuel Hand Date: Mon, 11 Jun 2018 07:11:04 +0100 Subject: Add trust management utilities to the omemo plugin --- plugins/omemo/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) (limited to 'plugins/omemo/CMakeLists.txt') diff --git a/plugins/omemo/CMakeLists.txt b/plugins/omemo/CMakeLists.txt index 50cd6627..bcec941b 100644 --- a/plugins/omemo/CMakeLists.txt +++ b/plugins/omemo/CMakeLists.txt @@ -13,6 +13,7 @@ find_packages(OMEMO_PACKAGES REQUIRED set(RESOURCE_LIST account_settings_dialog.ui + contact_details_dialog.ui ) compile_gresources( @@ -32,7 +33,9 @@ SOURCES src/account_settings_widget.vala src/bundle.vala src/contact_details_provider.vala + src/contact_details_dialog.vala src/database.vala + src/device_notification_populator.vala src/encrypt_state.vala src/encryption_list_entry.vala src/manager.vala -- cgit v1.2.3 From acbc5710d083e69f887b0b5a15e48b7d10b48190 Mon Sep 17 00:00:00 2001 From: Samuel Hand Date: Mon, 9 Jul 2018 14:16:23 +0100 Subject: Enable key management for own keys --- plugins/omemo/CMakeLists.txt | 2 - plugins/omemo/data/account_settings_dialog.ui | 124 ------------------------- plugins/omemo/data/contact_details_dialog.ui | 40 ++++++++ plugins/omemo/src/account_settings_dialog.vala | 54 ----------- plugins/omemo/src/account_settings_widget.vala | 2 +- plugins/omemo/src/contact_details_dialog.vala | 44 ++++++++- plugins/omemo/src/manager.vala | 9 ++ 7 files changed, 90 insertions(+), 185 deletions(-) delete mode 100644 plugins/omemo/data/account_settings_dialog.ui delete mode 100644 plugins/omemo/src/account_settings_dialog.vala (limited to 'plugins/omemo/CMakeLists.txt') diff --git a/plugins/omemo/CMakeLists.txt b/plugins/omemo/CMakeLists.txt index bcec941b..1c049588 100644 --- a/plugins/omemo/CMakeLists.txt +++ b/plugins/omemo/CMakeLists.txt @@ -12,7 +12,6 @@ find_packages(OMEMO_PACKAGES REQUIRED ) set(RESOURCE_LIST - account_settings_dialog.ui contact_details_dialog.ui ) @@ -28,7 +27,6 @@ compile_gresources( vala_precompile(OMEMO_VALA_C SOURCES - src/account_settings_dialog.vala src/account_settings_entry.vala src/account_settings_widget.vala src/bundle.vala diff --git a/plugins/omemo/data/account_settings_dialog.ui b/plugins/omemo/data/account_settings_dialog.ui deleted file mode 100644 index 31996d05..00000000 --- a/plugins/omemo/data/account_settings_dialog.ui +++ /dev/null @@ -1,124 +0,0 @@ - - - - \ No newline at end of file diff --git a/plugins/omemo/data/contact_details_dialog.ui b/plugins/omemo/data/contact_details_dialog.ui index 4844fdb9..4dadbb6a 100644 --- a/plugins/omemo/data/contact_details_dialog.ui +++ b/plugins/omemo/data/contact_details_dialog.ui @@ -9,6 +9,46 @@ True 40 40 + + + 12 + horizontal + False + + + True + Own fingerprint + 0 + 1 + True + 2 + + + + + + + + + + False + 18 + + + never + automatic + True + True + + + True + True + + + + + + 12 diff --git a/plugins/omemo/src/account_settings_dialog.vala b/plugins/omemo/src/account_settings_dialog.vala deleted file mode 100644 index 76357dbb..00000000 --- a/plugins/omemo/src/account_settings_dialog.vala +++ /dev/null @@ -1,54 +0,0 @@ -using Gtk; -using Qlite; -using Dino.Entities; - -namespace Dino.Plugins.Omemo { - -[GtkTemplate (ui = "/im/dino/Dino/omemo/account_settings_dialog.ui")] -public class AccountSettingsDialog : Gtk.Dialog { - - private Plugin plugin; - private Account account; - private string fingerprint; - - [GtkChild] private Label own_fingerprint; - [GtkChild] private ListBox other_list; - - public AccountSettingsDialog(Plugin plugin, Account account) { - Object(use_header_bar : 1); - this.plugin = plugin; - this.account = account; - - string own_b64 = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.identity_key_public_base64]; - fingerprint = fingerprint_from_base64(own_b64); - own_fingerprint.set_markup(fingerprint_markup(fingerprint)); - - int own_id = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id]; - - int i = 0; - foreach (Row row in plugin.db.identity_meta.with_address(account.id, account.bare_jid.to_string())) { - if (row[plugin.db.identity_meta.device_id] == own_id) continue; - if (i == 0) { - other_list.foreach((widget) => { widget.destroy(); }); - } - string? other_b64 = row[plugin.db.identity_meta.identity_key_public_base64]; - Label lbl = new Label(other_b64 != null ? fingerprint_markup(fingerprint_from_base64(other_b64)) : _("Unknown device (0x%.8x)").printf(row[plugin.db.identity_meta.device_id])) { use_markup = true, visible = true, margin = 8, selectable=true }; - if (row[plugin.db.identity_meta.now_active] && other_b64 != null) { - other_list.insert(lbl, 0); - } else { - lbl.sensitive = false; - other_list.insert(lbl, i); - } - i++; - } - } - - [GtkCallback] - public void copy_button_clicked() { - Clipboard.get_default(get_display()).set_text(fingerprint, fingerprint.length); - } - - -} - -} diff --git a/plugins/omemo/src/account_settings_widget.vala b/plugins/omemo/src/account_settings_widget.vala index 6db193fc..6148da56 100644 --- a/plugins/omemo/src/account_settings_widget.vala +++ b/plugins/omemo/src/account_settings_widget.vala @@ -27,7 +27,7 @@ public class AccountSettingWidget : Plugins.AccountSettingsWidget, Box { btn.valign = Align.CENTER; btn.clicked.connect(() => { activated(); - AccountSettingsDialog dialog = new AccountSettingsDialog(plugin, account); + ContactDetailsDialog dialog = new ContactDetailsDialog(plugin, account, account.bare_jid); dialog.set_transient_for((Window) get_toplevel()); dialog.present(); }); diff --git a/plugins/omemo/src/contact_details_dialog.vala b/plugins/omemo/src/contact_details_dialog.vala index aa1b2a5f..70bf1dd7 100644 --- a/plugins/omemo/src/contact_details_dialog.vala +++ b/plugins/omemo/src/contact_details_dialog.vala @@ -12,10 +12,15 @@ public class ContactDetailsDialog : Gtk.Dialog { private Plugin plugin; private Account account; private Jid jid; + private bool own = false; + private int own_id = 0; private Gee.List toggles; [GtkChild] private Grid fingerprints; + [GtkChild] private Box own_fingerprint_label; + [GtkChild] private Frame own_fingerprint_container; + [GtkChild] private Grid own_fingerprint; [GtkChild] private Box fingerprints_prompt_label; [GtkChild] private Frame fingerprints_prompt_container; [GtkChild] private Grid fingerprints_prompt; @@ -33,10 +38,12 @@ public class ContactDetailsDialog : Gtk.Dialog { .with(plugin.db.identity_meta.device_id, "=", device[plugin.db.identity_meta.device_id]) .set(plugin.db.identity_meta.trust_level, trust_level).perform(); - if(!trust) { - plugin.app.stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).untrust_device(jid, device[plugin.db.identity_meta.device_id]); - } else { - plugin.app.stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).trust_device(jid, device[plugin.db.identity_meta.device_id]); + if (!own) { + if(!trust) { + plugin.app.stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).untrust_device(jid, device[plugin.db.identity_meta.device_id]); + } else { + plugin.app.stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).trust_device(jid, device[plugin.db.identity_meta.device_id]); + } } } @@ -65,11 +72,34 @@ public class ContactDetailsDialog : Gtk.Dialog { toggles = new ArrayList(); + if(jid.equals(account.bare_jid)) { + own = true; + own_id = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id]; + own_fingerprint_label.visible = true; + own_fingerprint_container.visible = true; + string own_b64 = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.identity_key_public_base64]; + string fingerprint = fingerprint_from_base64(own_b64); + Label lbl = new Label(fingerprint_markup(fingerprint)) + { use_markup=true, justify=Justification.RIGHT, visible=true, margin = 8, halign = Align.START }; + + Box box = new Box(Gtk.Orientation.HORIZONTAL, 0) { visible = true, valign = Align.CENTER, hexpand = true, margin = 8 }; + + Button copy = new Button() { visible = true, valign = Align.CENTER, halign = Align.END, hexpand = false }; + copy.image = new Image.from_icon_name("edit-copy-symbolic", IconSize.BUTTON); + copy.clicked.connect(() => {Clipboard.get_default(get_display()).set_text(fingerprint, fingerprint.length);}); + box.pack_start(lbl); + box.pack_end(copy); + own_fingerprint.attach(box, 0, 0); + } + int i = 0; foreach (Row device in plugin.db.identity_meta.with_address(account.id, jid.to_string()).with(plugin.db.identity_meta.trust_level, "!=", Database.IdentityMetaTable.TrustLevel.UNKNOWN).with(plugin.db.identity_meta.trust_level, "!=", Database.IdentityMetaTable.TrustLevel.VERIFIED)) { if (device[plugin.db.identity_meta.identity_key_public_base64] == null) { continue; } + if(own && device[plugin.db.identity_meta.device_id] == own_id) { + continue; + } add_fingerprint(device, i, (Database.IdentityMetaTable.TrustLevel) device[plugin.db.identity_meta.trust_level]); i++; @@ -81,6 +111,9 @@ public class ContactDetailsDialog : Gtk.Dialog { if (device[plugin.db.identity_meta.identity_key_public_base64] == null) { continue; } + if(own && device[plugin.db.identity_meta.device_id] == own_id) { + continue; + } string res = fingerprint_markup(fingerprint_from_base64(device[plugin.db.identity_meta.identity_key_public_base64])); Label lbl = new Label(res) @@ -143,6 +176,9 @@ public class ContactDetailsDialog : Gtk.Dialog { int k = 0; foreach (Row device in plugin.db.identity_meta.with_address(account.id, jid.to_string()).without_null(plugin.db.identity_meta.identity_key_public_base64).with(plugin.db.identity_meta.trust_level, "=", Database.IdentityMetaTable.TrustLevel.VERIFIED)) { + if(own && device[plugin.db.identity_meta.device_id] == own_id) { + continue; + } string res = fingerprint_markup(fingerprint_from_base64(device[plugin.db.identity_meta.identity_key_public_base64])); Label lbl = new Label(res) { use_markup=true, justify=Justification.RIGHT, visible=true, margin = 8, halign = Align.START }; diff --git a/plugins/omemo/src/manager.vala b/plugins/omemo/src/manager.vala index e1f3ee56..2f641196 100644 --- a/plugins/omemo/src/manager.vala +++ b/plugins/omemo/src/manager.vala @@ -98,6 +98,15 @@ public class Manager : StreamInteractionModule, Object { return; } StreamModule module = (!)module_; + + foreach (Row row in db.identity_meta.with_address(conversation.account.id, conversation.account.bare_jid.to_string())){ + if(row[db.identity_meta.trust_level] == Database.IdentityMetaTable.TrustLevel.TRUSTED || row[db.identity_meta.trust_level] == Database.IdentityMetaTable.TrustLevel.VERIFIED){ + module.trust_device(conversation.account.bare_jid, row[db.identity_meta.device_id]); + } else { + module.untrust_device(conversation.account.bare_jid, row[db.identity_meta.device_id]); + } + } + EncryptState enc_state = module.encrypt(message_stanza, conversation.account.bare_jid); MessageState state; lock (message_states) { -- cgit v1.2.3 From 62ad56af216410d51ebfd6fa542e936703465549 Mon Sep 17 00:00:00 2001 From: Samuel Hand Date: Wed, 11 Jul 2018 13:20:02 +0100 Subject: Notify on a new own device --- plugins/omemo/CMakeLists.txt | 1 + plugins/omemo/src/own_notifications.vala | 38 ++++++++++++++++++++++++++++++++ plugins/omemo/src/plugin.vala | 1 + 3 files changed, 40 insertions(+) create mode 100644 plugins/omemo/src/own_notifications.vala (limited to 'plugins/omemo/CMakeLists.txt') diff --git a/plugins/omemo/CMakeLists.txt b/plugins/omemo/CMakeLists.txt index 1c049588..dda85f15 100644 --- a/plugins/omemo/CMakeLists.txt +++ b/plugins/omemo/CMakeLists.txt @@ -34,6 +34,7 @@ SOURCES src/contact_details_dialog.vala src/database.vala src/device_notification_populator.vala + src/own_notifications.vala src/encrypt_state.vala src/encryption_list_entry.vala src/manager.vala diff --git a/plugins/omemo/src/own_notifications.vala b/plugins/omemo/src/own_notifications.vala new file mode 100644 index 00000000..df0c4740 --- /dev/null +++ b/plugins/omemo/src/own_notifications.vala @@ -0,0 +1,38 @@ +using Dino.Entities; +using Xmpp; +using Gtk; + +namespace Dino.Plugins.Omemo { + +public class OwnNotifications { + + private StreamInteractor stream_interactor; + private Plugin plugin; + private Account account; + + public OwnNotifications (Plugin plugin, StreamInteractor stream_interactor, Account account) { + this.stream_interactor = (!)stream_interactor; + this.plugin = plugin; + this.account = account; + stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).bundle_fetched.connect_after((jid, device_id, bundle) => { + if (jid.equals(account.bare_jid) && has_new_devices(account.bare_jid)) { + display_notification(); + } + }); + if (has_new_devices(account.bare_jid)) { + display_notification(); + } + + } + + public bool has_new_devices(Jid jid) { + return plugin.db.identity_meta.with_address(account.id, jid.bare_jid.to_string()).with(plugin.db.identity_meta.trust_level, "=", Database.IdentityMetaTable.TrustLevel.UNKNOWN).without_null(plugin.db.identity_meta.identity_key_public_base64).count() > 0; + } + + private void display_notification() { + Notification notification = new Notification("Trust decision required"); + notification.set_body(@"A new OMEMO device has been added for the account $(account.bare_jid)"); + plugin.app.send_notification(account.id.to_string()+"-new-device", notification); + } +} +} diff --git a/plugins/omemo/src/plugin.vala b/plugins/omemo/src/plugin.vala index 79e6a5eb..b63ed3f2 100644 --- a/plugins/omemo/src/plugin.vala +++ b/plugins/omemo/src/plugin.vala @@ -44,6 +44,7 @@ public class Plugin : RootInterface, Object { this.app.plugin_registry.register_notification_populator(device_notification_populator); this.app.stream_interactor.module_manager.initialize_account_modules.connect((account, list) => { list.add(new StreamModule()); + new OwnNotifications(this, this.app.stream_interactor, account); }); Manager.start(this.app.stream_interactor, db); -- cgit v1.2.3 From 7e156b3a7510eaad212dfe0c72dc8aba8bda0e57 Mon Sep 17 00:00:00 2001 From: Samuel Hand Date: Wed, 25 Jul 2018 21:27:26 +0100 Subject: Code cleanup: create new trust management class --- plugins/omemo/CMakeLists.txt | 1 + plugins/omemo/src/contact_details_dialog.vala | 8 -- plugins/omemo/src/manager.vala | 91 ++++++------ plugins/omemo/src/stream_module.vala | 197 +++----------------------- plugins/omemo/src/trust_manager.vala | 128 +++++++++++++++++ 5 files changed, 196 insertions(+), 229 deletions(-) create mode 100644 plugins/omemo/src/trust_manager.vala (limited to 'plugins/omemo/CMakeLists.txt') diff --git a/plugins/omemo/CMakeLists.txt b/plugins/omemo/CMakeLists.txt index dda85f15..80665efd 100644 --- a/plugins/omemo/CMakeLists.txt +++ b/plugins/omemo/CMakeLists.txt @@ -45,6 +45,7 @@ SOURCES src/session_store.vala src/signed_pre_key_store.vala src/stream_module.vala + src/trust_manager.vala src/util.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/signal-protocol.vapi diff --git a/plugins/omemo/src/contact_details_dialog.vala b/plugins/omemo/src/contact_details_dialog.vala index 70bf1dd7..11bed0c1 100644 --- a/plugins/omemo/src/contact_details_dialog.vala +++ b/plugins/omemo/src/contact_details_dialog.vala @@ -37,14 +37,6 @@ public class ContactDetailsDialog : Gtk.Dialog { .with(plugin.db.identity_meta.address_name, "=", device[plugin.db.identity_meta.address_name]) .with(plugin.db.identity_meta.device_id, "=", device[plugin.db.identity_meta.device_id]) .set(plugin.db.identity_meta.trust_level, trust_level).perform(); - - if (!own) { - if(!trust) { - plugin.app.stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).untrust_device(jid, device[plugin.db.identity_meta.device_id]); - } else { - plugin.app.stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).trust_device(jid, device[plugin.db.identity_meta.device_id]); - } - } } private void add_fingerprint(Row device, int row, Database.IdentityMetaTable.TrustLevel trust) { diff --git a/plugins/omemo/src/manager.vala b/plugins/omemo/src/manager.vala index aa2a5351..5bd1e00e 100644 --- a/plugins/omemo/src/manager.vala +++ b/plugins/omemo/src/manager.vala @@ -12,6 +12,7 @@ public class Manager : StreamInteractionModule, Object { private StreamInteractor stream_interactor; private Database db; + private TrustManager trust_manager; private Map message_states = new HashMap(Entities.Message.hash_func, Entities.Message.equals_func); private ReceivedMessageListener received_message_listener = new ReceivedMessageListener(); @@ -64,6 +65,8 @@ public class Manager : StreamInteractionModule, Object { this.stream_interactor = stream_interactor; this.db = db; + this.trust_manager = new TrustManager(stream_interactor, db); + stream_interactor.stream_negotiated.connect(on_stream_negotiated); stream_interactor.account_added.connect(on_account_added); stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(received_message_listener); @@ -85,9 +88,12 @@ public class Manager : StreamInteractionModule, Object { } } - private Gee.List get_occupants(Jid muc, Account account){ + private Gee.List get_occupants(Jid jid, Account account){ Gee.List occupants = new ArrayList(Jid.equals_bare_func); - Gee.List? occupant_jids = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(muc, account); + if(!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat(jid, account)){ + occupants.add(jid); + } + Gee.List? occupant_jids = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(jid, account); if(occupant_jids == null) { return occupants; } @@ -113,14 +119,6 @@ public class Manager : StreamInteractionModule, Object { } StreamModule module = (!)module_; - foreach (Row row in db.identity_meta.with_address(conversation.account.id, conversation.account.bare_jid.to_string())){ - if(row[db.identity_meta.trust_level] == Database.IdentityMetaTable.TrustLevel.TRUSTED || row[db.identity_meta.trust_level] == Database.IdentityMetaTable.TrustLevel.VERIFIED){ - module.trust_device(conversation.account.bare_jid, row[db.identity_meta.device_id]); - } else { - module.untrust_device(conversation.account.bare_jid, row[db.identity_meta.device_id]); - } - } - Gee.List recipients; if (message_stanza.type_ == MessageStanza.TYPE_GROUPCHAT) { recipients = get_occupants((!)message.to.bare_jid, conversation.account); @@ -133,7 +131,7 @@ public class Manager : StreamInteractionModule, Object { recipients.add(message_stanza.to); } - EncryptState enc_state = module.encrypt(message_stanza, conversation.account.bare_jid, recipients); + EncryptState enc_state = trust_manager.encrypt(message_stanza, conversation.account.bare_jid, recipients, stream, conversation.account); MessageState state; lock (message_states) { if (message_states.has_key(message)) { @@ -156,13 +154,17 @@ public class Manager : StreamInteractionModule, Object { if (Plugin.DEBUG) print(@"OMEMO: message will be delayed: $state\n"); if (state.waiting_own_sessions > 0) { - module.fetch_bundles((!)stream, conversation.account.bare_jid); + module.fetch_bundles((!)stream, conversation.account.bare_jid, trust_manager.get_trusted_devices(conversation.account, conversation.account.bare_jid)); } if (state.waiting_other_sessions > 0 && message.counterpart != null) { - module.fetch_bundles((!)stream, ((!)message.counterpart).bare_jid); + foreach(Jid jid in get_occupants(((!)message.counterpart).bare_jid, conversation.account)) { + module.fetch_bundles((!)stream, jid, trust_manager.get_trusted_devices(conversation.account, jid)); + } } if (state.waiting_other_devicelists > 0 && message.counterpart != null) { - module.request_user_devicelist((!)stream, ((!)message.counterpart).bare_jid); + foreach(Jid jid in get_occupants(((!)message.counterpart).bare_jid, conversation.account)) { + module.request_user_devicelist((!)stream, jid); + } } } } @@ -171,7 +173,7 @@ public class Manager : StreamInteractionModule, Object { private void on_account_added(Account account) { stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).store_created.connect((store) => on_store_created(account, store)); - stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).device_list_loaded.connect((jid) => on_device_list_loaded(account, jid)); + stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).device_list_loaded.connect((jid, devices) => on_device_list_loaded(account, jid, devices)); stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).bundle_fetched.connect((jid, device_id, bundle) => on_bundle_fetched(account, jid, device_id, bundle)); } @@ -179,31 +181,8 @@ public class Manager : StreamInteractionModule, Object { stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).request_user_devicelist(stream, account.bare_jid); } - private void on_device_list_loaded(Account account, Jid jid) { + private void on_device_list_loaded(Account account, Jid jid, ArrayList device_list) { if (Plugin.DEBUG) print(@"OMEMO: received device list for $(account.bare_jid) from $jid\n"); - HashSet send_now = new HashSet(); - lock (message_states) { - foreach (Entities.Message msg in message_states.keys) { - if (!msg.account.equals(account)) continue; - Gee.List occupants = get_occupants(msg.counterpart.bare_jid, account); - MessageState state = message_states[msg]; - if (account.bare_jid.equals(jid)) { - state.waiting_own_devicelist = false; - } else if (msg.counterpart != null && (msg.counterpart.equals_bare(jid) || occupants.contains(jid))) { - state.waiting_other_devicelists--; - } - if (state.should_retry_now()) { - send_now.add(msg); - state.active_send_attempt = true; - } - } - } - foreach (Entities.Message msg in send_now) { - if (msg.counterpart == null) continue; - Entities.Conversation? conv = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(((!)msg.counterpart), account); - if (conv == null) continue; - stream_interactor.get_module(MessageProcessor.IDENTITY).send_xmpp_message(msg, (!)conv, true); - } // Update meta database XmppStream? stream = stream_interactor.get_stream(account); @@ -215,7 +194,6 @@ public class Manager : StreamInteractionModule, Object { return; } - ArrayList device_list = module.get_device_list(jid); db.identity_meta.insert_device_list(account.id, jid.bare_jid.to_string(), device_list); int inc = 0; foreach (Row row in db.identity_meta.with_address(account.id, jid.bare_jid.to_string()).with_null(db.identity_meta.identity_key_public_base64)) { @@ -229,6 +207,31 @@ public class Manager : StreamInteractionModule, Object { if (db.trust.select().with(db.trust.identity_id, "=", account.id).with(db.trust.address_name, "=", jid.bare_jid.to_string()).count() == 0) { db.trust.insert().value(db.trust.identity_id, account.id).value(db.trust.address_name, jid.bare_jid.to_string()).value(db.trust.blind_trust, true).perform(); } + + HashSet send_now = new HashSet(); + lock (message_states) { + foreach (Entities.Message msg in message_states.keys) { + if (!msg.account.equals(account)) continue; + Gee.List occupants = get_occupants(msg.counterpart.bare_jid, account); + MessageState state = message_states[msg]; + if (account.bare_jid.equals(jid)) { + state.waiting_own_devicelist = false; + } else if (msg.counterpart != null && occupants.contains(jid)) { + state.waiting_other_devicelists--; + } + if (state.should_retry_now()) { + send_now.add(msg); + state.active_send_attempt = true; + } + } + } + foreach (Entities.Message msg in send_now) { + if (msg.counterpart == null) continue; + Entities.Conversation? conv = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(((!)msg.counterpart), account); + if (conv == null) continue; + stream_interactor.get_module(MessageProcessor.IDENTITY).send_xmpp_message(msg, (!)conv, true); + } + } public void on_bundle_fetched(Account account, Jid jid, int32 device_id, Bundle bundle) { @@ -264,9 +267,7 @@ public class Manager : StreamInteractionModule, Object { MessageState state = message_states[msg]; - if (trusted != Database.IdentityMetaTable.TrustLevel.TRUSTED && trusted != Database.IdentityMetaTable.TrustLevel.VERIFIED) { - module.untrust_device(jid, device_id); - } else { + if (trusted == Database.IdentityMetaTable.TrustLevel.TRUSTED || trusted == Database.IdentityMetaTable.TrustLevel.VERIFIED) { if(account.bare_jid.equals(jid) || (msg.counterpart != null && (msg.counterpart.equals_bare(jid) || occupants.contains(jid)))) { session_created = module.start_session(stream, jid, device_id, bundle); } @@ -339,14 +340,14 @@ public class Manager : StreamInteractionModule, Object { if (flag == null) return false; if (flag.has_room_feature(conversation.counterpart, Xep.Muc.Feature.NON_ANONYMOUS) && flag.has_room_feature(conversation.counterpart, Xep.Muc.Feature.MEMBERS_ONLY)) { foreach(Jid jid in stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(conversation.counterpart, conversation.account)) { - if (!((!)module).is_known_address(jid.bare_jid)) return false; + if (!trust_manager.is_known_address(conversation.account, jid.bare_jid)) return false; } return true; } else { return false; } } else { - return ((!)module).is_known_address(conversation.counterpart.bare_jid); + return trust_manager.is_known_address(conversation.account, conversation.counterpart.bare_jid); } } diff --git a/plugins/omemo/src/stream_module.vala b/plugins/omemo/src/stream_module.vala index 13406738..d298db2b 100644 --- a/plugins/omemo/src/stream_module.vala +++ b/plugins/omemo/src/stream_module.vala @@ -16,137 +16,16 @@ private const int NUM_KEYS_TO_PUBLISH = 100; public class StreamModule : XmppStreamModule { public static Xmpp.ModuleIdentity IDENTITY = new Xmpp.ModuleIdentity(NS_URI, "omemo_module"); - private Store store; + public Store store { public get; private set; } private ConcurrentSet active_bundle_requests = new ConcurrentSet(); private ConcurrentSet active_devicelist_requests = new ConcurrentSet(); - private Map> device_lists = new HashMap>(Jid.hash_bare_func, Jid.equals_bare_func); private Map> ignored_devices = new HashMap>(Jid.hash_bare_func, Jid.equals_bare_func); - private Map> occupants = new HashMap>(Jid.hash_bare_func, Jid.equals_bare_func); private ReceivedPipelineListener received_pipeline_listener; public signal void store_created(Store store); - public signal void device_list_loaded(Jid jid); + public signal void device_list_loaded(Jid jid, ArrayList devices); public signal void bundle_fetched(Jid jid, int device_id, Bundle bundle); - public EncryptState encrypt(MessageStanza message, Jid self_jid, Gee.List recipients) { - EncryptState status = new EncryptState(); - if (!Plugin.ensure_context()) return status; - if (message.to == null) return status; - - if(message.type_ == MessageStanza.TYPE_GROUPCHAT) { - occupants[message.to] = recipients; - } - - try { - if (!device_lists.has_key(self_jid)) return status; - status.own_list = true; - status.own_devices = device_lists.get(self_jid).size; - status.other_waiting_lists = 0; - status.other_devices = 0; - foreach (Jid recipient in recipients) { - if (!device_lists.has_key(recipient)) { - status.other_waiting_lists++; - return status; - } - status.other_devices += device_lists.get(recipient).size; - } - if (status.own_devices == 0 || status.other_devices == 0) return status; - - uint8[] key = new uint8[16]; - Plugin.get_context().randomize(key); - uint8[] iv = new uint8[16]; - Plugin.get_context().randomize(iv); - - uint8[] ciphertext = aes_encrypt(Cipher.AES_GCM_NOPADDING, key, iv, message.body.data); - - StanzaNode header; - StanzaNode encrypted = new StanzaNode.build("encrypted", NS_URI).add_self_xmlns() - .put_node(header = new StanzaNode.build("header", NS_URI) - .put_attribute("sid", store.local_registration_id.to_string()) - .put_node(new StanzaNode.build("iv", NS_URI) - .put_node(new StanzaNode.text(Base64.encode(iv))))) - .put_node(new StanzaNode.build("payload", NS_URI) - .put_node(new StanzaNode.text(Base64.encode(ciphertext)))); - - Address address = new Address(message.to.bare_jid.to_string(), 0); - foreach (Jid recipient in recipients) { - foreach(int32 device_id in device_lists[recipient]) { - if (is_ignored_device(recipient, device_id)) { - status.other_lost++; - continue; - } - try { - address.name = recipient.bare_jid.to_string(); - address.device_id = (int) device_id; - StanzaNode key_node = create_encrypted_key(key, address); - header.put_node(key_node); - status.other_success++; - } catch (Error e) { - if (e.code == ErrorCode.UNKNOWN) status.other_unknown++; - else status.other_failure++; - } - } - } - address.name = self_jid.bare_jid.to_string(); - foreach(int32 device_id in device_lists[self_jid]) { - if (is_ignored_device(self_jid, device_id)) { - status.own_lost++; - continue; - } - if (device_id != store.local_registration_id) { - address.device_id = (int) device_id; - try { - StanzaNode key_node = create_encrypted_key(key, address); - header.put_node(key_node); - status.own_success++; - } catch (Error e) { - if (e.code == ErrorCode.UNKNOWN) status.own_unknown++; - else status.own_failure++; - } - } - } - - message.stanza.put_node(encrypted); - Xep.ExplicitEncryption.add_encryption_tag_to_message(message, NS_URI, "OMEMO"); - message.body = "[This message is OMEMO encrypted]"; - status.encrypted = true; - } catch (Error e) { - if (Plugin.DEBUG) print(@"OMEMO: Signal error while encrypting message: $(e.message)\n"); - } - return status; - } - - public void untrust_device(Jid jid, int device_id) { - if (device_lists.has_key(jid) && device_lists[jid].contains(device_id)) { - device_lists[jid].remove(device_id); - } - if (store.contains_session(new Address(jid.bare_jid.to_string(), device_id))) { - store.delete_session(new Address(jid.bare_jid.to_string(), device_id)); - } - } - - public void trust_device(Jid jid, int device_id) { - if (is_ignored_device(jid, device_id)){ - ignored_devices[jid].remove(device_id); - } - if (!device_lists.has_key(jid)) { - device_lists[jid] = new ArrayList(); - } - if (!device_lists[jid].contains(device_id)) { - device_lists[jid].add(device_id); - } - } - - private StanzaNode create_encrypted_key(uint8[] key, Address address) throws GLib.Error { - SessionCipher cipher = store.create_session_cipher(address); - CiphertextMessage device_key = cipher.encrypt(key); - StanzaNode key_node = new StanzaNode.build("key", NS_URI) - .put_attribute("rid", address.device_id.to_string()) - .put_node(new StanzaNode.text(Base64.encode(device_key.serialized))); - if (device_key.type == CiphertextType.PREKEY) key_node.put_attribute("prekey", "true"); - return key_node; - } - public override void attach(XmppStream stream) { if (!Plugin.ensure_context()) return; @@ -162,18 +41,9 @@ public class StreamModule : XmppStreamModule { } public void request_user_devicelist(XmppStream stream, Jid jid) { - Gee.List recipients; - if (occupants.contains(jid)) { - recipients = occupants.get(jid); - } else { - recipients = new ArrayList(Jid.equals_bare_func); - recipients.add(jid); - } - foreach (Jid recipient in recipients) { - if (active_devicelist_requests.add(recipient)) { - if (Plugin.DEBUG) print(@"OMEMO: requesting device list for $jid\n"); - stream.get_module(Pubsub.Module.IDENTITY).request(stream, recipient, NODE_DEVICELIST, (stream, jid, id, node) => on_devicelist(stream, jid, id, node)); - } + if (active_devicelist_requests.add(jid)) { + if (Plugin.DEBUG) print(@"OMEMO: requesting device list for $jid\n"); + stream.get_module(Pubsub.Module.IDENTITY).request(stream, jid, NODE_DEVICELIST, (stream, jid, id, node) => on_devicelist(stream, jid, id, node)); } } @@ -197,43 +67,30 @@ public class StreamModule : XmppStreamModule { publish_bundles_if_needed(stream, jid); } } - lock(device_lists) { - device_lists[jid] = new ArrayList(); - foreach (StanzaNode device_node in node.get_subnodes("device")) { - device_lists[jid].add(device_node.get_attribute_int("id")); - } + + ArrayList device_list = new ArrayList(); + foreach (StanzaNode device_node in node.get_subnodes("device")) { + device_list.add(device_node.get_attribute_int("id")); } active_devicelist_requests.remove(jid); - device_list_loaded(jid); + device_list_loaded(jid, device_list); } - public void fetch_bundles(XmppStream stream, Jid jid) { - Gee.List recipients; - if (occupants.contains(jid)) { - recipients = occupants.get(jid); - } else { - recipients = new ArrayList(Jid.equals_bare_func); - recipients.add(jid); - } - foreach (Jid recipient in recipients) { - if (!device_lists.has_key(recipient)) { - return; - } - Address address = new Address(recipient.bare_jid.to_string(), 0); - foreach(int32 device_id in device_lists[recipient]) { - if (!is_ignored_device(recipient, device_id)) { - address.device_id = device_id; - try { - if (!store.contains_session(address)) { - fetch_bundle(stream, recipient, device_id); - } - } catch (Error e) { - // Ignore + public void fetch_bundles(XmppStream stream, Jid jid, Gee.List devices) { + Address address = new Address(jid.bare_jid.to_string(), 0); + foreach(int32 device_id in devices) { + if (!is_ignored_device(jid, device_id)) { + address.device_id = device_id; + try { + if (!store.contains_session(address)) { + fetch_bundle(stream, jid, device_id); } + } catch (Error e) { + // Ignore } } - address.device_id = 0; } + address.device_id = 0; } public void fetch_bundle(XmppStream stream, Jid jid, int device_id) { @@ -245,18 +102,6 @@ public class StreamModule : XmppStreamModule { } } - public ArrayList get_device_list(Jid jid) { - if (is_known_address(jid)) { - return device_lists[jid]; - } else { - return new ArrayList(); - } - } - - public bool is_known_address(Jid jid) { - return device_lists.has_key(jid); - } - public void ignore_device(Jid jid, int32 device_id) { if (device_id <= 0) return; lock (ignored_devices) { diff --git a/plugins/omemo/src/trust_manager.vala b/plugins/omemo/src/trust_manager.vala new file mode 100644 index 00000000..408faed8 --- /dev/null +++ b/plugins/omemo/src/trust_manager.vala @@ -0,0 +1,128 @@ +using Dino.Entities; +using Gee; +using Xmpp; +using Signal; +using Qlite; + +namespace Dino.Plugins.Omemo { + +public class TrustManager { + + private StreamInteractor stream_interactor; + private Database db; + + public TrustManager(StreamInteractor stream_interactor, Database db) { + this.stream_interactor = stream_interactor; + this.db = db; + } + + private StanzaNode create_encrypted_key(uint8[] key, Address address, Store store) throws GLib.Error { + SessionCipher cipher = store.create_session_cipher(address); + CiphertextMessage device_key = cipher.encrypt(key); + StanzaNode key_node = new StanzaNode.build("key", NS_URI) + .put_attribute("rid", address.device_id.to_string()) + .put_node(new StanzaNode.text(Base64.encode(device_key.serialized))); + if (device_key.type == CiphertextType.PREKEY) key_node.put_attribute("prekey", "true"); + return key_node; + } + + public EncryptState encrypt(MessageStanza message, Jid self_jid, Gee.List recipients, XmppStream stream, Account account) { + EncryptState status = new EncryptState(); + if (!Plugin.ensure_context()) return status; + if (message.to == null) return status; + + StreamModule module = stream.get_module(StreamModule.IDENTITY); + + try { + if (!is_known_address(account, self_jid)) return status; + status.own_list = true; + status.own_devices = get_trusted_devices(account, self_jid).size; + status.other_waiting_lists = 0; + status.other_devices = 0; + foreach (Jid recipient in recipients) { + if (!is_known_address(account, recipient)) { + status.other_waiting_lists++; + return status; + } + status.other_devices += get_trusted_devices(account, recipient).size; + } + if (status.own_devices == 0 || status.other_devices == 0) return status; + + uint8[] key = new uint8[16]; + Plugin.get_context().randomize(key); + uint8[] iv = new uint8[16]; + Plugin.get_context().randomize(iv); + + uint8[] ciphertext = aes_encrypt(Cipher.AES_GCM_NOPADDING, key, iv, message.body.data); + + StanzaNode header; + StanzaNode encrypted = new StanzaNode.build("encrypted", NS_URI).add_self_xmlns() + .put_node(header = new StanzaNode.build("header", NS_URI) + .put_attribute("sid", module.store.local_registration_id.to_string()) + .put_node(new StanzaNode.build("iv", NS_URI) + .put_node(new StanzaNode.text(Base64.encode(iv))))) + .put_node(new StanzaNode.build("payload", NS_URI) + .put_node(new StanzaNode.text(Base64.encode(ciphertext)))); + + Address address = new Address(message.to.bare_jid.to_string(), 0); + foreach (Jid recipient in recipients) { + foreach(int32 device_id in get_trusted_devices(account, recipient)) { + if (module.is_ignored_device(recipient, device_id)) { + status.other_lost++; + continue; + } + try { + address.name = recipient.bare_jid.to_string(); + address.device_id = (int) device_id; + StanzaNode key_node = create_encrypted_key(key, address, module.store); + header.put_node(key_node); + status.other_success++; + } catch (Error e) { + if (e.code == ErrorCode.UNKNOWN) status.other_unknown++; + else status.other_failure++; + } + } + } + address.name = self_jid.bare_jid.to_string(); + foreach(int32 device_id in get_trusted_devices(account, self_jid)) { + if (module.is_ignored_device(self_jid, device_id)) { + status.own_lost++; + continue; + } + if (device_id != module.store.local_registration_id) { + address.device_id = (int) device_id; + try { + StanzaNode key_node = create_encrypted_key(key, address, module.store); + header.put_node(key_node); + status.own_success++; + } catch (Error e) { + if (e.code == ErrorCode.UNKNOWN) status.own_unknown++; + else status.own_failure++; + } + } + } + + message.stanza.put_node(encrypted); + Xep.ExplicitEncryption.add_encryption_tag_to_message(message, NS_URI, "OMEMO"); + message.body = "[This message is OMEMO encrypted]"; + status.encrypted = true; + } catch (Error e) { + if (Plugin.DEBUG) print(@"OMEMO: Signal error while encrypting message: $(e.message)\n"); + } + return status; + } + + public bool is_known_address(Account account, Jid jid) { + return db.identity_meta.with_address(account.id, jid.to_string()).count() > 0; + } + + public Gee.List get_trusted_devices(Account account, Jid jid) { + Gee.List devices = new ArrayList(); + foreach (Row device in db.identity_meta.with_address(account.id, jid.to_string()).with(db.identity_meta.trust_level, "!=", Database.IdentityMetaTable.TrustLevel.UNKNOWN).with(db.identity_meta.trust_level, "!=", Database.IdentityMetaTable.TrustLevel.UNTRUSTED).without_null(db.identity_meta.identity_key_public_base64)) { + devices.add(device[db.identity_meta.device_id]); + } + return devices; + } +} + +} -- cgit v1.2.3 From 01d26bffd890c08dfd374631c498fec614cdf430 Mon Sep 17 00:00:00 2001 From: Samuel Hand Date: Fri, 3 Aug 2018 19:07:23 +0100 Subject: UI update: make the trust management interface more user friendly --- plugins/omemo/CMakeLists.txt | 2 + plugins/omemo/data/contact_details_dialog.ui | 249 ++++++++++---------- plugins/omemo/data/manage_key_dialog.ui | 156 +++++++++++++ plugins/omemo/src/contact_details_dialog.vala | 312 +++++++++++++------------- plugins/omemo/src/manage_key_dialog.vala | 193 ++++++++++++++++ 5 files changed, 630 insertions(+), 282 deletions(-) create mode 100644 plugins/omemo/data/manage_key_dialog.ui create mode 100644 plugins/omemo/src/manage_key_dialog.vala (limited to 'plugins/omemo/CMakeLists.txt') diff --git a/plugins/omemo/CMakeLists.txt b/plugins/omemo/CMakeLists.txt index 80665efd..5126d05b 100644 --- a/plugins/omemo/CMakeLists.txt +++ b/plugins/omemo/CMakeLists.txt @@ -13,6 +13,7 @@ find_packages(OMEMO_PACKAGES REQUIRED set(RESOURCE_LIST contact_details_dialog.ui + manage_key_dialog.ui ) compile_gresources( @@ -37,6 +38,7 @@ SOURCES src/own_notifications.vala src/encrypt_state.vala src/encryption_list_entry.vala + src/manage_key_dialog.vala src/manager.vala src/message_flag.vala src/plugin.vala diff --git a/plugins/omemo/data/contact_details_dialog.ui b/plugins/omemo/data/contact_details_dialog.ui index 4dadbb6a..856c7af4 100644 --- a/plugins/omemo/data/contact_details_dialog.ui +++ b/plugins/omemo/data/contact_details_dialog.ui @@ -2,47 +2,75 @@ + + False + show_qrcode + left + True + + + True + 10 + + + True + + + + + diff --git a/plugins/omemo/src/contact_details_dialog.vala b/plugins/omemo/src/contact_details_dialog.vala index b14a108a..575437ff 100644 --- a/plugins/omemo/src/contact_details_dialog.vala +++ b/plugins/omemo/src/contact_details_dialog.vala @@ -3,6 +3,8 @@ using Xmpp; using Gee; using Qlite; using Dino.Entities; +using Qrencode; +using Gdk; namespace Dino.Plugins.Omemo { @@ -23,6 +25,9 @@ public class ContactDetailsDialog : Gtk.Dialog { [GtkChild] private ListBox keys; [GtkChild] private Switch auto_accept; [GtkChild] private Button copy; + [GtkChild] private Button show_qrcode; + [GtkChild] private Image qrcode; + [GtkChild] private Popover qrcode_popover; private void set_device_trust(Row device, bool trust) { Database.IdentityMetaTable.TrustLevel trust_level = trust ? Database.IdentityMetaTable.TrustLevel.TRUSTED : Database.IdentityMetaTable.TrustLevel.UNTRUSTED; @@ -87,7 +92,7 @@ public class ContactDetailsDialog : Gtk.Dialog { if(row == lbr) { Row updated_device = plugin.db.identity_meta.with_address(device[plugin.db.identity_meta.identity_id], device[plugin.db.identity_meta.address_name]).with(plugin.db.identity_meta.device_id, "=", device[plugin.db.identity_meta.device_id]).single().row().inner; ManageKeyDialog manage_dialog = new ManageKeyDialog(updated_device, plugin.db); - manage_dialog.set_transient_for((Window) get_toplevel()); + manage_dialog.set_transient_for((Gtk.Window) get_toplevel()); manage_dialog.present(); manage_dialog.response.connect((response) => { set_row(response, device[plugin.db.identity_meta.now_active], img, status_lbl, lbl, lbr); @@ -184,6 +189,12 @@ public class ContactDetailsDialog : Gtk.Dialog { own_fingerprint.set_markup(fingerprint_markup(fingerprint)); copy.clicked.connect(() => {Clipboard.get_default(get_display()).set_text(fingerprint, fingerprint.length);}); + + int sid = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id]; + Pixbuf pixbuf = new QRcode(@"xmpp:$(account.bare_jid)?omemo-sid-$(sid)=$(fingerprint)", 2).to_pixbuf(); + pixbuf = pixbuf.scale_simple(150, 150, InterpType.NEAREST); + qrcode.set_from_pixbuf(pixbuf); + show_qrcode.clicked.connect(qrcode_popover.popup); } new_keys.set_header_func((row, before_row) => { diff --git a/plugins/omemo/vapi/qrencode.vapi b/plugins/omemo/vapi/qrencode.vapi new file mode 100644 index 00000000..54c201ab --- /dev/null +++ b/plugins/omemo/vapi/qrencode.vapi @@ -0,0 +1,50 @@ +using Gdk; + +[CCode (cheader_filename = "qrencode.h")] +namespace Qrencode { + + [CCode (cname = "QRecLevel", cprefix = "QR_ECLEVEL_")] + public enum ECLevel { + L, + M, + Q, + H + } + + [CCode (cname = "QRencodeMode", cprefix = "QR_MODE_")] + public enum EncodeMode { + NUL, + NUM, + AN, + [CCode (cname = "QR_MODE_8")] + EIGHT_BIT, + KANJI, + STRUCTURE, + ECI, + FNC1FIRST, + FNC1SECOND + } + + [CCode (cname = "QRcode", free_function = "QRcode_free", has_type_id = false)] + [Compact] + public class QRcode { + private int version; + private int width; + [CCode (array_length = false)] + private uint8[] data; + + [CCode (cname = "QRcode_encodeString")] + public QRcode (string str, int version = 0, ECLevel level = ECLevel.L, EncodeMode hint = EncodeMode.EIGHT_BIT, bool casesensitive = true); + + public Pixbuf to_pixbuf() { + uint8[] bitmap = new uint8[3*width*width]; + for (int i = 0; i < width*width; i++) { + uint8 color = (data[i] & 1) == 1 ? 0 : 255; + bitmap[i*3] = color; + bitmap[i*3+1] = color; + bitmap[i*3+2] = color; + } + return new Pixbuf.from_data(bitmap, Colorspace.RGB, false, 8, width, width, width*3); + } + } +} -- cgit v1.2.3