From 08a5088c16ae0bd69adc42ac6489adde3a9ad13f Mon Sep 17 00:00:00 2001 From: fiaxh Date: Fri, 2 Aug 2019 03:15:12 +0200 Subject: Rework encryption enabling logic + UI --- plugins/gpgme-vala/vapi/gpgme.vapi | 3 +- plugins/omemo/src/logic/manager.vala | 29 ++++++- plugins/omemo/src/logic/trust_manager.vala | 111 ++++++++++++------------ plugins/omemo/src/protocol/stream_module.vala | 38 +++++--- plugins/omemo/src/ui/encryption_list_entry.vala | 28 +++++- plugins/openpgp/src/encryption_list_entry.vala | 30 +++++-- plugins/openpgp/src/plugin.vala | 2 +- 7 files changed, 157 insertions(+), 84 deletions(-) (limited to 'plugins') diff --git a/plugins/gpgme-vala/vapi/gpgme.vapi b/plugins/gpgme-vala/vapi/gpgme.vapi index e66aee1f..3b8e660d 100644 --- a/plugins/gpgme-vala/vapi/gpgme.vapi +++ b/plugins/gpgme-vala/vapi/gpgme.vapi @@ -22,9 +22,8 @@ * */ -[CCode (lower_case_cprefix = "gpgme_", cheader_filename = "gpgme.h")] +[CCode (lower_case_cprefix = "gpgme_", cheader_filename = "gpgme.h,gpgme_fix.h")] namespace GPG { - [CCode (cheader_filename = "gpgme_fix.h")] public static GLib.RecMutex global_mutex; [CCode (cname = "struct _gpgme_engine_info")] diff --git a/plugins/omemo/src/logic/manager.vala b/plugins/omemo/src/logic/manager.vala index b8862ab8..3fe41a35 100644 --- a/plugins/omemo/src/logic/manager.vala +++ b/plugins/omemo/src/logic/manager.vala @@ -149,7 +149,7 @@ public class Manager : StreamInteractionModule, Object { } if (state.waiting_other_devicelists > 0 && message.counterpart != null) { foreach(Jid jid in get_occupants(((!)message.counterpart).bare_jid, conversation.account)) { - module.request_user_devicelist((!)stream, jid); + module.request_user_devicelist.begin((!)stream, jid); } } } @@ -161,7 +161,7 @@ public class Manager : StreamInteractionModule, Object { XmppStream? stream = stream_interactor.get_stream(account); if(stream == null) return; - stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).request_user_devicelist((!)stream, jid); + stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).request_user_devicelist.begin((!)stream, jid); } private void on_account_added(Account account) { @@ -171,7 +171,7 @@ public class Manager : StreamInteractionModule, Object { } private void on_stream_negotiated(Account account, XmppStream stream) { - stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).request_user_devicelist(stream, account.bare_jid); + stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).request_user_devicelist.begin(stream, account.bare_jid); } private void on_device_list_loaded(Account account, Jid jid, ArrayList device_list) { @@ -352,7 +352,7 @@ public class Manager : StreamInteractionModule, Object { if (stream == null) return; StreamModule? module = ((!)stream).get_module(StreamModule.IDENTITY); if(module == null) return; - module.request_user_devicelist(stream, account.bare_jid); + module.request_user_devicelist.begin(stream, account.bare_jid); } } @@ -376,6 +376,27 @@ public class Manager : StreamInteractionModule, Object { return trust_manager.is_known_address(conversation.account, conversation.counterpart.bare_jid); } + public async bool ensure_get_keys_for_conversation(Conversation conversation) { + if (stream_interactor.get_module(MucManager.IDENTITY).is_private_room(conversation.account, conversation.counterpart)) { + foreach (Jid offline_member in stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(conversation.counterpart, conversation.account)) { + bool ok = yield ensure_get_keys_for_jid(conversation.account, offline_member); + if (!ok) { + return false; + } + } + return true; + } + + return yield ensure_get_keys_for_jid(conversation.account, conversation.counterpart.bare_jid); + } + + public async bool ensure_get_keys_for_jid(Account account, Jid jid) { + if (trust_manager.is_known_address(account, jid)) return true; + XmppStream? stream = stream_interactor.get_stream(account); + var device_list = yield stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).request_user_devicelist((!)stream, jid); + return device_list.size > 0; + } + public static void start(StreamInteractor stream_interactor, Database db, TrustManager trust_manager) { Manager m = new Manager(stream_interactor, db, trust_manager); stream_interactor.add_module(m); diff --git a/plugins/omemo/src/logic/trust_manager.vala b/plugins/omemo/src/logic/trust_manager.vala index 7758de75..ded7e995 100644 --- a/plugins/omemo/src/logic/trust_manager.vala +++ b/plugins/omemo/src/logic/trust_manager.vala @@ -269,70 +269,67 @@ public class TrustManager { if (sid <= 0) return false; foreach (StanzaNode key_node in header.get_subnodes("key")) { if (key_node.get_attribute_int("rid") == store.local_registration_id) { - try { - string? payload = encrypted.get_deep_string_content("payload"); - string? iv_node = header.get_deep_string_content("iv"); - string? key_node_content = key_node.get_string_content(); - if (payload == null || iv_node == null || key_node_content == null) continue; - uint8[] key; - uint8[] ciphertext = Base64.decode((!)payload); - uint8[] iv = Base64.decode((!)iv_node); - Gee.List possible_jids = new ArrayList(); - if (conversation.type_ == Conversation.Type.CHAT) { - possible_jids.add(stanza.from); + + string? payload = encrypted.get_deep_string_content("payload"); + string? iv_node = header.get_deep_string_content("iv"); + string? key_node_content = key_node.get_string_content(); + if (payload == null || iv_node == null || key_node_content == null) continue; + uint8[] key; + uint8[] ciphertext = Base64.decode((!)payload); + uint8[] iv = Base64.decode((!)iv_node); + Gee.List possible_jids = new ArrayList(); + if (conversation.type_ == Conversation.Type.CHAT) { + possible_jids.add(stanza.from); + } else { + Jid? real_jid = message.real_jid; + if (real_jid != null) { + possible_jids.add(real_jid); } else { - Jid? real_jid = message.real_jid; - if (real_jid != null) { - possible_jids.add(real_jid); - } else { - // If we don't know the device name (MUC history w/o MAM), test decryption with all keys with fitting device id - foreach (Row row in db.identity_meta.get_with_device_id(sid)) { - possible_jids.add(new Jid(row[db.identity_meta.address_name])); - } + // If we don't know the device name (MUC history w/o MAM), test decryption with all keys with fitting device id + foreach (Row row in db.identity_meta.get_with_device_id(sid)) { + possible_jids.add(new Jid(row[db.identity_meta.address_name])); } } + } - foreach (Jid possible_jid in possible_jids) { - try { - Address address = new Address(possible_jid.bare_jid.to_string(), header.get_attribute_int("sid")); - if (key_node.get_attribute_bool("prekey")) { - PreKeySignalMessage msg = Plugin.get_context().deserialize_pre_key_signal_message(Base64.decode((!)key_node_content)); - SessionCipher cipher = store.create_session_cipher(address); - key = cipher.decrypt_pre_key_signal_message(msg); - } else { - SignalMessage msg = Plugin.get_context().deserialize_signal_message(Base64.decode((!)key_node_content)); - SessionCipher cipher = store.create_session_cipher(address); - key = cipher.decrypt_signal_message(msg); - } - //address.device_id = 0; // TODO: Hack to have address obj live longer - - if (key.length >= 32) { - int authtaglength = key.length - 16; - uint8[] new_ciphertext = new uint8[ciphertext.length + authtaglength]; - uint8[] new_key = new uint8[16]; - Memory.copy(new_ciphertext, ciphertext, ciphertext.length); - Memory.copy((uint8*)new_ciphertext + ciphertext.length, (uint8*)key + 16, authtaglength); - Memory.copy(new_key, key, 16); - ciphertext = new_ciphertext; - key = new_key; - } - - message.body = arr_to_str(aes_decrypt(Cipher.AES_GCM_NOPADDING, key, iv, ciphertext)); - message_device_id_map[message] = address.device_id; - message.encryption = Encryption.OMEMO; - flag.decrypted = true; - } catch (Error e) { - continue; + foreach (Jid possible_jid in possible_jids) { + try { + Address address = new Address(possible_jid.bare_jid.to_string(), header.get_attribute_int("sid")); + if (key_node.get_attribute_bool("prekey")) { + PreKeySignalMessage msg = Plugin.get_context().deserialize_pre_key_signal_message(Base64.decode((!)key_node_content)); + SessionCipher cipher = store.create_session_cipher(address); + key = cipher.decrypt_pre_key_signal_message(msg); + } else { + SignalMessage msg = Plugin.get_context().deserialize_signal_message(Base64.decode((!)key_node_content)); + SessionCipher cipher = store.create_session_cipher(address); + key = cipher.decrypt_signal_message(msg); } - - // If we figured out which real jid a message comes from due to decryption working, save it - if (conversation.type_ == Conversation.Type.GROUPCHAT && message.real_jid == null) { - message.real_jid = possible_jid; + //address.device_id = 0; // TODO: Hack to have address obj live longer + + if (key.length >= 32) { + int authtaglength = key.length - 16; + uint8[] new_ciphertext = new uint8[ciphertext.length + authtaglength]; + uint8[] new_key = new uint8[16]; + Memory.copy(new_ciphertext, ciphertext, ciphertext.length); + Memory.copy((uint8*)new_ciphertext + ciphertext.length, (uint8*)key + 16, authtaglength); + Memory.copy(new_key, key, 16); + ciphertext = new_ciphertext; + key = new_key; } - break; + + message.body = arr_to_str(aes_decrypt(Cipher.AES_GCM_NOPADDING, key, iv, ciphertext)); + message_device_id_map[message] = address.device_id; + message.encryption = Encryption.OMEMO; + flag.decrypted = true; + } catch (Error e) { + continue; } - } catch (Error e) { - warning(@"Signal error while decrypting message: $(e.message)\n"); + + // If we figured out which real jid a message comes from due to decryption working, save it + if (conversation.type_ == Conversation.Type.GROUPCHAT && message.real_jid == null) { + message.real_jid = possible_jid; + } + break; } } } diff --git a/plugins/omemo/src/protocol/stream_module.vala b/plugins/omemo/src/protocol/stream_module.vala index 0e4e962d..258ff8c0 100644 --- a/plugins/omemo/src/protocol/stream_module.vala +++ b/plugins/omemo/src/protocol/stream_module.vala @@ -17,7 +17,7 @@ public class StreamModule : XmppStreamModule { public Store store { public get; private set; } private ConcurrentSet active_bundle_requests = new ConcurrentSet(); - private ConcurrentSet active_devicelist_requests = new ConcurrentSet(); + private HashMap>> active_devicelist_requests = new HashMap>>(Jid.hash_func, Jid.equals_func); private Map> ignored_devices = new HashMap>(Jid.hash_bare_func, Jid.equals_bare_func); public signal void store_created(Store store); @@ -29,22 +29,40 @@ public class StreamModule : XmppStreamModule { this.store = Plugin.get_context().create_store(); store_created(store); - stream.get_module(Pubsub.Module.IDENTITY).add_filtered_notification(stream, NODE_DEVICELIST, (stream, jid, id, node) => on_devicelist(stream, jid, id, node)); + stream.get_module(Pubsub.Module.IDENTITY).add_filtered_notification(stream, NODE_DEVICELIST, (stream, jid, id, node) => parse_device_list(stream, jid, id, node)); } public override void detach(XmppStream stream) {} - public void request_user_devicelist(XmppStream stream, Jid jid) { - if (active_devicelist_requests.add(jid)) { - debug("requesting device list for %s", jid.to_string()); - stream.get_module(Pubsub.Module.IDENTITY).request(stream, jid, NODE_DEVICELIST, (stream, jid, id, node) => on_devicelist(stream, jid, id, node)); + public async ArrayList request_user_devicelist(XmppStream stream, Jid jid) { + var future = active_devicelist_requests[jid]; + if (future == null) { + var promise = new Promise?>(); + future = promise.future; + active_devicelist_requests[jid] = future; + + stream.get_module(Pubsub.Module.IDENTITY).request(stream, jid, NODE_DEVICELIST, (stream, jid, id, node) => { + ArrayList device_list = parse_device_list(stream, jid, id, node); + promise.set_value(device_list); + active_devicelist_requests.unset(jid); + }); + } + + try { + ArrayList device_list = yield future.wait_async(); + return device_list; + } catch (FutureError error) { + warning("Future error when waiting for device list: %s", error.message); + return new ArrayList(); } } - public void on_devicelist(XmppStream stream, Jid jid, string? id, StanzaNode? node_) { + public ArrayList parse_device_list(XmppStream stream, Jid jid, string? id, StanzaNode? node_) { + ArrayList device_list = new ArrayList(); + StanzaNode node = node_ ?? new StanzaNode.build("list", NS_URI).add_self_xmlns(); Jid? my_jid = stream.get_flag(Bind.Flag.IDENTITY).my_jid; - if (my_jid == null) return; + if (my_jid == null) return device_list; if (jid.equals_bare(my_jid) && store.local_registration_id != 0) { bool am_on_devicelist = false; foreach (StanzaNode device_node in node.get_subnodes("device")) { @@ -61,12 +79,12 @@ public class StreamModule : XmppStreamModule { publish_bundles_if_needed(stream, jid); } - 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); + + return device_list; } public void fetch_bundles(XmppStream stream, Jid jid, Gee.List devices) { diff --git a/plugins/omemo/src/ui/encryption_list_entry.vala b/plugins/omemo/src/ui/encryption_list_entry.vala index 2e8905e2..a4891442 100644 --- a/plugins/omemo/src/ui/encryption_list_entry.vala +++ b/plugins/omemo/src/ui/encryption_list_entry.vala @@ -1,3 +1,5 @@ +using Xmpp; + namespace Dino.Plugins.Omemo { public class EncryptionListEntry : Plugins.EncryptionListEntry, Object { @@ -15,9 +17,29 @@ public class EncryptionListEntry : Plugins.EncryptionListEntry, Object { return "OMEMO"; }} - public bool can_encrypt(Entities.Conversation conversation) { - return plugin.app.stream_interactor.get_module(Manager.IDENTITY).can_encrypt(conversation); + public void encryption_activated(Entities.Conversation conversation, Plugins.SetInputFieldStatus input_status_callback) { + encryption_activated_async.begin(conversation, input_status_callback); } -} + public async void encryption_activated_async(Entities.Conversation conversation, Plugins.SetInputFieldStatus input_status_callback) { + MucManager muc_manager = plugin.app.stream_interactor.get_module(MucManager.IDENTITY); + Manager omemo_manager = plugin.app.stream_interactor.get_module(Manager.IDENTITY); + + if (muc_manager.is_private_room(conversation.account, conversation.counterpart)) { + foreach (Jid offline_member in muc_manager.get_offline_members(conversation.counterpart, conversation.account)) { + bool ok = yield omemo_manager.ensure_get_keys_for_jid(conversation.account, offline_member); + if (!ok) { + input_status_callback(new Plugins.InputFieldStatus("A member does not support OMEMO: %s".printf(offline_member.to_string()), Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); + return; + } + } + return; + } + + if (!(yield omemo_manager.ensure_get_keys_for_jid(conversation.account, conversation.counterpart.bare_jid))) { + input_status_callback(new Plugins.InputFieldStatus("This contact does not support %s encryption".printf("OMEMO"), Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); + } + } + +} } diff --git a/plugins/openpgp/src/encryption_list_entry.vala b/plugins/openpgp/src/encryption_list_entry.vala index 603c5c94..5b89ec1c 100644 --- a/plugins/openpgp/src/encryption_list_entry.vala +++ b/plugins/openpgp/src/encryption_list_entry.vala @@ -8,9 +8,11 @@ namespace Dino.Plugins.OpenPgp { private class EncryptionListEntry : Plugins.EncryptionListEntry, Object { private StreamInteractor stream_interactor; + private Database db; - public EncryptionListEntry(StreamInteractor stream_interactor) { + public EncryptionListEntry(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; + this.db = db; } public Entities.Encryption encryption { get { @@ -21,12 +23,25 @@ private class EncryptionListEntry : Plugins.EncryptionListEntry, Object { return "OpenPGP"; }} - public bool can_encrypt(Entities.Conversation conversation) { + public void encryption_activated(Entities.Conversation conversation, Plugins.SetInputFieldStatus input_status_callback) { + try { + GPGHelper.get_public_key(db.get_account_key(conversation.account) ?? ""); + } catch (Error e) { + input_status_callback(new Plugins.InputFieldStatus("You didn't configure OpenPGP for this account. You can do that in the Accounts Dialog.", Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); + return; + } + if (conversation.type_ == Conversation.Type.CHAT) { string? key_id = stream_interactor.get_module(Manager.IDENTITY).get_key_id(conversation.account, conversation.counterpart); + if (key_id == null) { + input_status_callback(new Plugins.InputFieldStatus("This contact does not support %s encryption.".printf("OpenPGP"), Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); + return; + } try { - return key_id != null && GPGHelper.get_keylist(key_id).size > 0; - } catch (Error e) { return false; } + GPGHelper.get_keylist(key_id); + } catch (Error e) { + input_status_callback(new Plugins.InputFieldStatus("This contact's OpenPGP key is not in your keyring.", Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); + } } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { Gee.List muc_jids = new Gee.ArrayList(); Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); @@ -36,11 +51,12 @@ private class EncryptionListEntry : Plugins.EncryptionListEntry, Object { foreach (Jid jid in muc_jids) { string? key_id = stream_interactor.get_module(Manager.IDENTITY).get_key_id(conversation.account, jid); - if (key_id == null) return false; + if (key_id == null) { + input_status_callback(new Plugins.InputFieldStatus("A member's OpenPGP key is not in your keyring: %s / %s.".printf(jid.to_string(), key_id), Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); + return; + } } - return true; } - return false; } } diff --git a/plugins/openpgp/src/plugin.vala b/plugins/openpgp/src/plugin.vala index 35ede01e..b4581f31 100644 --- a/plugins/openpgp/src/plugin.vala +++ b/plugins/openpgp/src/plugin.vala @@ -19,7 +19,7 @@ public class Plugin : Plugins.RootInterface, Object { public void registered(Dino.Application app) { this.app = app; this.db = new Database(Path.build_filename(Application.get_storage_dir(), "pgp.db")); - this.list_entry = new EncryptionListEntry(app.stream_interactor); + this.list_entry = new EncryptionListEntry(app.stream_interactor, db); this.settings_entry = new AccountSettingsEntry(this); this.contact_details_provider = new ContactDetailsProvider(app.stream_interactor); -- cgit v1.2.3-54-g00ecf