From cdb4d77259e6c361aaca64a483a43d7441f4803d Mon Sep 17 00:00:00 2001 From: fiaxh Date: Fri, 19 Mar 2021 23:07:40 +0100 Subject: Add support for unencrypted RTP calls to libdino Co-authored-by: Marvin W --- libdino/src/plugin/interfaces.vala | 25 +++++++++++++++++++++++++ libdino/src/plugin/loader.vala | 2 +- libdino/src/plugin/registry.vala | 1 + 3 files changed, 27 insertions(+), 1 deletion(-) (limited to 'libdino/src/plugin') diff --git a/libdino/src/plugin/interfaces.vala b/libdino/src/plugin/interfaces.vala index dab058af..8be77895 100644 --- a/libdino/src/plugin/interfaces.vala +++ b/libdino/src/plugin/interfaces.vala @@ -84,6 +84,31 @@ public abstract interface ConversationAdditionPopulator : ConversationItemPopula public virtual void populate_timespan(Conversation conversation, DateTime from, DateTime to) { } } +public abstract interface VideoCallPlugin : Object { + // Video widget + public abstract VideoCallWidget? create_widget(WidgetType type); + + // Devices + public signal void devices_changed(string media, bool incoming); + public abstract Gee.List get_devices(string media, bool incoming); + public abstract MediaDevice? get_device(Xmpp.Xep.JingleRtp.Stream stream, bool incoming); + public abstract void set_pause(Xmpp.Xep.JingleRtp.Stream stream, bool pause); + public abstract void set_device(Xmpp.Xep.JingleRtp.Stream stream, MediaDevice? device); +} + +public abstract interface VideoCallWidget : Object { + public signal void resolution_changed(uint width, uint height); + public abstract void display_stream(Xmpp.Xep.JingleRtp.Stream stream); // TODO: Multi participant + public abstract void display_device(MediaDevice device); + public abstract void detach(); +} + +public abstract interface MediaDevice : Object { + public abstract string id { get; } + public abstract string display_name { get; } + public abstract string detail_name { get; } +} + public abstract interface NotificationPopulator : Object { public abstract string id { get; } public abstract void init(Conversation conversation, NotificationCollection summary, WidgetType type); diff --git a/libdino/src/plugin/loader.vala b/libdino/src/plugin/loader.vala index 102bf3f9..8b0d93ad 100644 --- a/libdino/src/plugin/loader.vala +++ b/libdino/src/plugin/loader.vala @@ -26,7 +26,7 @@ public class Loader : Object { this.search_paths = app.search_path_generator.get_plugin_paths(); } - public void loadAll() throws Error { + public void load_all() throws Error { if (Module.supported() == false) { throw new Error(-1, 0, "Plugins are not supported"); } diff --git a/libdino/src/plugin/registry.vala b/libdino/src/plugin/registry.vala index e3f73855..27d72b80 100644 --- a/libdino/src/plugin/registry.vala +++ b/libdino/src/plugin/registry.vala @@ -12,6 +12,7 @@ public class Registry { internal Gee.Collection conversation_titlebar_entries = new Gee.TreeSet((a, b) => { return (int)(a.order - b.order); }); + public VideoCallPlugin? video_call_plugin; public bool register_encryption_list_entry(EncryptionListEntry entry) { lock(encryption_list_entries) { -- cgit v1.2.3-70-g09d2 From 5d85b6cdb0165d863aadd25d9a73707b8f5cc83e Mon Sep 17 00:00:00 2001 From: fiaxh Date: Sat, 17 Apr 2021 14:50:31 +0200 Subject: Handle non-existant call support --- libdino/src/plugin/interfaces.vala | 2 ++ libdino/src/service/calls.vala | 38 ++++++++++++++++++++--- main/src/ui/conversation_titlebar/call_entry.vala | 13 +++++--- plugins/rtp/src/codec_util.vala | 1 + plugins/rtp/src/module.vala | 6 ++-- plugins/rtp/src/plugin.vala | 16 ++++++++++ 6 files changed, 65 insertions(+), 11 deletions(-) (limited to 'libdino/src/plugin') diff --git a/libdino/src/plugin/interfaces.vala b/libdino/src/plugin/interfaces.vala index 8be77895..97951850 100644 --- a/libdino/src/plugin/interfaces.vala +++ b/libdino/src/plugin/interfaces.vala @@ -85,6 +85,8 @@ public abstract interface ConversationAdditionPopulator : ConversationItemPopula } public abstract interface VideoCallPlugin : Object { + + public abstract bool supports(string media); // Video widget public abstract VideoCallWidget? create_widget(WidgetType type); diff --git a/libdino/src/service/calls.vala b/libdino/src/service/calls.vala index b7374607..1d47823d 100644 --- a/libdino/src/service/calls.vala +++ b/libdino/src/service/calls.vala @@ -83,7 +83,7 @@ namespace Dino { we_should_send_video[call] = video; we_should_send_audio[call] = true; - if (yield has_jmi_resources(conversation)) { + if (has_jmi_resources(conversation)) { XmppStream? stream = stream_interactor.get_stream(conversation.account); jmi_call[conversation.account] = call; jmi_video[conversation.account] = video; @@ -245,8 +245,28 @@ namespace Dino { // If video_feed == null && !mute we're trying to mute a non-existant feed. It will be muted as soon as it is created. } - public async bool can_do_calls(Conversation conversation) { - return (yield get_call_resources(conversation)).size > 0 || yield has_jmi_resources(conversation); + public async bool can_do_audio_calls_async(Conversation conversation) { + if (!can_do_audio_calls()) return false; + return (yield get_call_resources(conversation)).size > 0 || has_jmi_resources(conversation); + } + + private bool can_do_audio_calls() { + Plugins.VideoCallPlugin? plugin = Application.get_default().plugin_registry.video_call_plugin; + if (plugin == null) return false; + + return plugin.supports("audio"); + } + + public async bool can_do_video_calls_async(Conversation conversation) { + if (!can_do_video_calls()) return false; + return (yield get_call_resources(conversation)).size > 0 || has_jmi_resources(conversation); + } + + private bool can_do_video_calls() { + Plugins.VideoCallPlugin? plugin = Application.get_default().plugin_registry.video_call_plugin; + if (plugin == null) return false; + + return plugin.supports("video"); } private async Gee.List get_call_resources(Conversation conversation) { @@ -266,7 +286,7 @@ namespace Dino { return ret; } - private async bool has_jmi_resources(Conversation conversation) { + private bool has_jmi_resources(Conversation conversation) { int64 jmi_resources = db.entity.select() .with(db.entity.jid_id, "=", db.get_jid_id(conversation.counterpart)) .join_with(db.entity_feature, db.entity.caps_hash, db.entity_feature.entity) @@ -289,6 +309,11 @@ namespace Dino { } private void on_incoming_call(Account account, Xep.Jingle.Session session) { + if (!can_do_audio_calls()) { + warning("Incoming call but no call support detected. Ignoring."); + return; + } + bool counterpart_wants_video = false; foreach (Xep.Jingle.Content content in session.contents) { Xep.JingleRtp.Parameters? rtp_content_parameter = content.content_params as Xep.JingleRtp.Parameters; @@ -550,6 +575,11 @@ namespace Dino { Xep.JingleMessageInitiation.Module mi_module = stream_interactor.module_manager.get_module(account, Xep.JingleMessageInitiation.Module.IDENTITY); mi_module.session_proposed.connect((from, to, sid, descriptions) => { + if (!can_do_audio_calls()) { + warning("Incoming call but no call support detected. Ignoring."); + return; + } + bool audio_requested = descriptions.any_match((description) => description.ns_uri == Xep.JingleRtp.NS_URI && description.get_attribute("media") == "audio"); bool video_requested = descriptions.any_match((description) => description.ns_uri == Xep.JingleRtp.NS_URI && description.get_attribute("media") == "video"); if (!audio_requested && !video_requested) return; diff --git a/main/src/ui/conversation_titlebar/call_entry.vala b/main/src/ui/conversation_titlebar/call_entry.vala index 5e28ecbe..e1d10e5c 100644 --- a/main/src/ui/conversation_titlebar/call_entry.vala +++ b/main/src/ui/conversation_titlebar/call_entry.vala @@ -34,6 +34,9 @@ namespace Dino.Ui { private StreamInteractor stream_interactor; private Conversation conversation; + private ModelButton audio_button = new ModelButton() { text="Audio call", visible=true }; + private ModelButton video_button = new ModelButton() { text="Video call", visible=true }; + public CallButton(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; @@ -42,7 +45,6 @@ namespace Dino.Ui { Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu(); Box box = new Box(Orientation.VERTICAL, 0) { margin=10, visible=true }; - ModelButton audio_button = new ModelButton() { text="Audio call", visible=true }; audio_button.clicked.connect(() => { stream_interactor.get_module(Calls.IDENTITY).initiate_call.begin(conversation, false, (_, res) => { Call call = stream_interactor.get_module(Calls.IDENTITY).initiate_call.end(res); @@ -50,7 +52,7 @@ namespace Dino.Ui { }); }); box.add(audio_button); - ModelButton video_button = new ModelButton() { text="Video call", visible=true }; + video_button.clicked.connect(() => { stream_interactor.get_module(Calls.IDENTITY).initiate_call.begin(conversation, true, (_, res) => { Call call = stream_interactor.get_module(Calls.IDENTITY).initiate_call.end(res); @@ -116,9 +118,12 @@ namespace Dino.Ui { private async void update_visibility() { if (conversation.type_ == Conversation.Type.CHAT) { Conversation conv_bak = conversation; - bool can_do_calls = yield stream_interactor.get_module(Calls.IDENTITY).can_do_calls(conversation); + bool audio_works = yield stream_interactor.get_module(Calls.IDENTITY).can_do_audio_calls_async(conversation); + bool video_works = yield stream_interactor.get_module(Calls.IDENTITY).can_do_audio_calls_async(conversation); if (conv_bak != conversation) return; - visible = can_do_calls; + + visible = audio_works; + video_button.visible = video_works; } else { visible = false; } diff --git a/plugins/rtp/src/codec_util.vala b/plugins/rtp/src/codec_util.vala index 7537c11d..6a2438f1 100644 --- a/plugins/rtp/src/codec_util.vala +++ b/plugins/rtp/src/codec_util.vala @@ -225,6 +225,7 @@ public class Dino.Plugins.Rtp.CodecUtil { } public string? get_encode_element_name(string media, string? codec) { + if (!is_element_supported(get_pay_element_name(media, codec))) return null; foreach (string candidate in get_encode_candidates(media, codec)) { if (is_element_supported(candidate)) return candidate; } diff --git a/plugins/rtp/src/module.vala b/plugins/rtp/src/module.vala index 52cc1880..13a21cd8 100644 --- a/plugins/rtp/src/module.vala +++ b/plugins/rtp/src/module.vala @@ -64,13 +64,13 @@ public class Dino.Plugins.Rtp.Module : JingleRtp.Module { } private async bool is_payload_supported(string media, JingleRtp.PayloadType payload_type) { - string codec = CodecUtil.get_codec_from_payload(media, payload_type); + string? codec = CodecUtil.get_codec_from_payload(media, payload_type); if (codec == null) return false; if (unsupported_codecs.contains(codec)) return false; if (supported_codecs.contains(codec)) return true; - string encode_element = codec_util.get_encode_element_name(media, codec); - string decode_element = codec_util.get_decode_element_name(media, codec); + string? encode_element = codec_util.get_encode_element_name(media, codec); + string? decode_element = codec_util.get_decode_element_name(media, codec); if (encode_element == null || decode_element == null) { debug("No suitable encoder or decoder found for %s", codec); unsupported_codecs.add(codec); diff --git a/plugins/rtp/src/plugin.vala b/plugins/rtp/src/plugin.vala index f0ad7db2..d43588b4 100644 --- a/plugins/rtp/src/plugin.vala +++ b/plugins/rtp/src/plugin.vala @@ -278,6 +278,22 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { Gst.deinit(); } + public bool supports(string media) { + if (rtpbin == null) return false; + + if (media == "audio") { + if (get_devices("audio", false).is_empty) return false; + if (get_devices("audio", true).is_empty) return false; + } + + if (media == "video") { + if (Gst.ElementFactory.make("gtksink", null) == null) return false; + if (get_devices("video", false).is_empty) return false; + } + + return true; + } + public VideoCallWidget? create_widget(WidgetType type) { if (type == WidgetType.GTK) { return new VideoWidget(this); -- cgit v1.2.3-70-g09d2 From 90f9ecf62b2ebfef14de2874e7942552409632bf Mon Sep 17 00:00:00 2001 From: fiaxh Date: Mon, 3 May 2021 13:17:17 +0200 Subject: Calls: Indicate whether OMEMO key is verified --- libdino/src/plugin/interfaces.vala | 10 +++ libdino/src/plugin/registry.vala | 8 +++ libdino/src/service/calls.vala | 4 +- main/CMakeLists.txt | 1 + main/src/ui/call_window/call_bottom_bar.vala | 64 +----------------- .../src/ui/call_window/call_encryption_button.vala | 77 ++++++++++++++++++++++ .../src/ui/call_window/call_window_controller.vala | 17 ++++- plugins/omemo/CMakeLists.txt | 1 + .../omemo/src/dtls_srtp_verification_draft.vala | 7 +- plugins/omemo/src/logic/decrypt.vala | 3 +- plugins/omemo/src/plugin.vala | 6 +- plugins/omemo/src/ui/call_encryption_entry.vala | 57 ++++++++++++++++ 12 files changed, 181 insertions(+), 74 deletions(-) create mode 100644 main/src/ui/call_window/call_encryption_button.vala create mode 100644 plugins/omemo/src/ui/call_encryption_entry.vala (limited to 'libdino/src/plugin') diff --git a/libdino/src/plugin/interfaces.vala b/libdino/src/plugin/interfaces.vala index 97951850..eadbb085 100644 --- a/libdino/src/plugin/interfaces.vala +++ b/libdino/src/plugin/interfaces.vala @@ -29,6 +29,16 @@ public interface EncryptionListEntry : Object { public abstract Object? get_encryption_icon(Entities.Conversation conversation, ContentItem content_item); } +public interface CallEncryptionEntry : Object { + public abstract CallEncryptionWidget? get_widget(Account account, Xmpp.Xep.Jingle.ContentEncryption encryption); +} + +public interface CallEncryptionWidget : Object { + public abstract string? get_title(); + public abstract bool show_keys(); + public abstract string? get_icon_name(); +} + public abstract class AccountSettingsEntry : Object { public abstract string id { get; } public virtual Priority priority { get { return Priority.DEFAULT; } } diff --git a/libdino/src/plugin/registry.vala b/libdino/src/plugin/registry.vala index 27d72b80..e28c4de7 100644 --- a/libdino/src/plugin/registry.vala +++ b/libdino/src/plugin/registry.vala @@ -4,6 +4,7 @@ namespace Dino.Plugins { public class Registry { internal ArrayList encryption_list_entries = new ArrayList(); + internal HashMap call_encryption_entries = new HashMap(); internal ArrayList account_settings_entries = new ArrayList(); internal ArrayList contact_details_entries = new ArrayList(); internal Map text_commands = new HashMap(); @@ -25,6 +26,13 @@ public class Registry { } } + public bool register_call_entryption_entry(string ns, CallEncryptionEntry entry) { + lock (call_encryption_entries) { + call_encryption_entries[ns] = entry; + } + return true; + } + public bool register_account_settings_entry(AccountSettingsEntry entry) { lock(account_settings_entries) { foreach(var e in account_settings_entries) { diff --git a/libdino/src/service/calls.vala b/libdino/src/service/calls.vala index a44b59fd..4c3bbea7 100644 --- a/libdino/src/service/calls.vala +++ b/libdino/src/service/calls.vala @@ -75,7 +75,7 @@ namespace Dino { call.account = conversation.account; call.counterpart = conversation.counterpart; call.ourpart = conversation.account.full_jid; - call.time = call.local_time = new DateTime.now_utc(); + call.time = call.local_time = call.end_time = new DateTime.now_utc(); call.state = Call.State.RINGING; stream_interactor.get_module(CallStore.IDENTITY).add_call(call, conversation); @@ -380,7 +380,7 @@ namespace Dino { call.counterpart = from; } call.account = account; - call.time = call.local_time = new DateTime.now_utc(); + call.time = call.local_time = call.end_time = new DateTime.now_utc(); call.state = Call.State.RINGING; Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(call.counterpart.bare_jid, account, Conversation.Type.CHAT); diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 69992f06..4891abb0 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -139,6 +139,7 @@ SOURCES src/ui/call_window/audio_settings_popover.vala src/ui/call_window/call_bottom_bar.vala + src/ui/call_window/call_encryption_button.vala src/ui/call_window/call_window.vala src/ui/call_window/call_window_controller.vala src/ui/call_window/video_settings_popover.vala diff --git a/main/src/ui/call_window/call_bottom_bar.vala b/main/src/ui/call_window/call_bottom_bar.vala index 64b157dd..8a0604b3 100644 --- a/main/src/ui/call_window/call_bottom_bar.vala +++ b/main/src/ui/call_window/call_bottom_bar.vala @@ -25,8 +25,7 @@ public class Dino.Ui.CallBottomBar : Gtk.Box { private MenuButton video_settings_button = new MenuButton() { halign=Align.END, valign=Align.END }; public VideoSettingsPopover? video_settings_popover; - private MenuButton encryption_button = new MenuButton() { relief=ReliefStyle.NONE, height_request=30, width_request=30, margin_start=20, margin_bottom=25, halign=Align.START, valign=Align.END }; - private Image encryption_image = new Image.from_icon_name("changes-allow-symbolic", IconSize.BUTTON) { visible=true }; + public CallEntryptionButton encryption_button = new CallEntryptionButton() { relief=ReliefStyle.NONE, height_request=30, width_request=30, margin_start=20, margin_bottom=25, halign=Align.START, valign=Align.END }; private Label label = new Label("") { margin=20, halign=Align.CENTER, valign=Align.CENTER, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, hexpand=true, visible=true }; private Stack stack = new Stack() { visible=true }; @@ -35,8 +34,6 @@ public class Dino.Ui.CallBottomBar : Gtk.Box { Object(orientation:Orientation.HORIZONTAL, spacing:0); Overlay default_control = new Overlay() { visible=true }; - encryption_button.add(encryption_image); - encryption_button.get_style_context().add_class("encryption-box"); default_control.add_overlay(encryption_button); Box main_buttons = new Box(Orientation.HORIZONTAL, 20) { margin_start=40, margin_end=40, margin=20, halign=Align.CENTER, hexpand=true, visible=true }; @@ -89,54 +86,6 @@ public class Dino.Ui.CallBottomBar : Gtk.Box { this.get_style_context().add_class("call-bottom-bar"); } - public void set_encryption(Xmpp.Xep.Jingle.ContentEncryption? audio_encryption, Xmpp.Xep.Jingle.ContentEncryption? video_encryption, bool same) { - encryption_button.visible = true; - - Popover popover = new Popover(encryption_button); - if (audio_encryption == null) { - encryption_image.set_from_icon_name("changes-allow-symbolic", IconSize.BUTTON); - encryption_button.get_style_context().add_class("unencrypted"); - - popover.add(new Label("This call isn't encrypted.") { margin=10, visible=true } ); - return; - } - - encryption_image.set_from_icon_name("changes-prevent-symbolic", IconSize.BUTTON); - encryption_button.get_style_context().remove_class("unencrypted"); - - Box box = new Box(Orientation.VERTICAL, 5) { margin=10, visible=true }; - if (audio_encryption.encryption_name == "OMEMO") { - box.add(new Label("This call is encrypted with OMEMO.") { use_markup=true, xalign=0, visible=true } ); - } else { - box.add(new Label("This call is end-to-end encrypted.") { use_markup=true, xalign=0, visible=true }); - } - - if (same) { - box.add(create_media_encryption_grid(audio_encryption)); - } else { - box.add(new Label("Audio") { use_markup=true, xalign=0, visible=true }); - box.add(create_media_encryption_grid(audio_encryption)); - box.add(new Label("Video") { use_markup=true, xalign=0, visible=true }); - box.add(create_media_encryption_grid(video_encryption)); - } - popover.add(box); - - encryption_button.set_popover(popover); - } - - private Grid create_media_encryption_grid(Xmpp.Xep.Jingle.ContentEncryption? encryption) { - Grid ret = new Grid() { row_spacing=3, column_spacing=5, visible=true }; - if (encryption.peer_key.length > 0) { - ret.attach(new Label("Peer call key") { xalign=0, visible=true }, 1, 2, 1, 1); - ret.attach(new Label("" + format_fingerprint(encryption.peer_key) + "") { use_markup=true, max_width_chars=25, ellipsize=EllipsizeMode.MIDDLE, xalign=0, hexpand=true, visible=true }, 2, 2, 1, 1); - } - if (encryption.our_key.length > 0) { - ret.attach(new Label("Your call key") { xalign=0, visible=true }, 1, 3, 1, 1); - ret.attach(new Label("" + format_fingerprint(encryption.our_key) + "") { use_markup=true, max_width_chars=25, ellipsize=EllipsizeMode.MIDDLE, xalign=0, hexpand=true, visible=true }, 2, 3, 1, 1); - } - return ret; - } - public AudioSettingsPopover? show_audio_device_choices(bool show) { audio_settings_button.visible = show; if (audio_settings_popover != null) audio_settings_popover.visible = false; @@ -212,15 +161,4 @@ public class Dino.Ui.CallBottomBar : Gtk.Box { public bool is_menu_active() { return video_settings_button.active || audio_settings_button.active || encryption_button.active; } - - private string format_fingerprint(uint8[] fingerprint) { - var sb = new StringBuilder(); - for (int i = 0; i < fingerprint.length; i++) { - sb.append("%02x".printf(fingerprint[i])); - if (i < fingerprint.length - 1) { - sb.append(":"); - } - } - return sb.str; - } } \ No newline at end of file diff --git a/main/src/ui/call_window/call_encryption_button.vala b/main/src/ui/call_window/call_encryption_button.vala new file mode 100644 index 00000000..1d785d51 --- /dev/null +++ b/main/src/ui/call_window/call_encryption_button.vala @@ -0,0 +1,77 @@ +using Dino.Entities; +using Gtk; +using Pango; + +public class Dino.Ui.CallEntryptionButton : MenuButton { + + private Image encryption_image = new Image.from_icon_name("changes-allow-symbolic", IconSize.BUTTON) { visible=true }; + + construct { + add(encryption_image); + get_style_context().add_class("encryption-box"); + this.set_popover(popover); + } + + public void set_icon(bool encrypted, string? icon_name) { + this.visible = true; + + if (encrypted) { + encryption_image.set_from_icon_name(icon_name ?? "changes-prevent-symbolic", IconSize.BUTTON); + get_style_context().remove_class("unencrypted"); + } else { + encryption_image.set_from_icon_name(icon_name ?? "changes-allow-symbolic", IconSize.BUTTON); + get_style_context().add_class("unencrypted"); + } + } + + public void set_info(string? title, bool show_keys, Xmpp.Xep.Jingle.ContentEncryption? audio_encryption, Xmpp.Xep.Jingle.ContentEncryption? video_encryption) { + Popover popover = new Popover(this); + this.set_popover(popover); + + if (audio_encryption == null) { + popover.add(new Label("This call is unencrypted.") { margin=10, visible=true } ); + return; + } + if (title != null && !show_keys) { + popover.add(new Label(title) { use_markup=true, margin=10, visible=true } ); + return; + } + + Box box = new Box(Orientation.VERTICAL, 10) { margin=10, visible=true }; + box.add(new Label("%s".printf(title ?? "This call is end-to-end encrypted.")) { use_markup=true, xalign=0, visible=true }); + + if (video_encryption == null) { + box.add(create_media_encryption_grid(audio_encryption)); + } else { + box.add(new Label("Audio") { use_markup=true, xalign=0, visible=true }); + box.add(create_media_encryption_grid(audio_encryption)); + box.add(new Label("Video") { use_markup=true, xalign=0, visible=true }); + box.add(create_media_encryption_grid(video_encryption)); + } + popover.add(box); + } + + private Grid create_media_encryption_grid(Xmpp.Xep.Jingle.ContentEncryption? encryption) { + Grid ret = new Grid() { row_spacing=3, column_spacing=5, visible=true }; + if (encryption.peer_key.length > 0) { + ret.attach(new Label("Peer call key") { xalign=0, visible=true }, 1, 2, 1, 1); + ret.attach(new Label("" + format_fingerprint(encryption.peer_key) + "") { use_markup=true, max_width_chars=25, ellipsize=EllipsizeMode.MIDDLE, xalign=0, hexpand=true, visible=true }, 2, 2, 1, 1); + } + if (encryption.our_key.length > 0) { + ret.attach(new Label("Your call key") { xalign=0, visible=true }, 1, 3, 1, 1); + ret.attach(new Label("" + format_fingerprint(encryption.our_key) + "") { use_markup=true, max_width_chars=25, ellipsize=EllipsizeMode.MIDDLE, xalign=0, hexpand=true, visible=true }, 2, 3, 1, 1); + } + return ret; + } + + private string format_fingerprint(uint8[] fingerprint) { + var sb = new StringBuilder(); + for (int i = 0; i < fingerprint.length; i++) { + sb.append("%02x".printf(fingerprint[i])); + if (i < fingerprint.length - 1) { + sb.append(":"); + } + } + return sb.str; + } +} \ No newline at end of file diff --git a/main/src/ui/call_window/call_window_controller.vala b/main/src/ui/call_window/call_window_controller.vala index 7e5920ce..b07b41b1 100644 --- a/main/src/ui/call_window/call_window_controller.vala +++ b/main/src/ui/call_window/call_window_controller.vala @@ -78,7 +78,22 @@ public class Dino.Ui.CallWindowController : Object { }); calls.encryption_updated.connect((call, audio_encryption, video_encryption, same) => { if (!this.call.equals(call)) return; - call_window.bottom_bar.set_encryption(audio_encryption, video_encryption, same); + + string? title = null; + string? icon_name = null; + bool show_keys = true; + Plugins.Registry registry = Dino.Application.get_default().plugin_registry; + Plugins.CallEncryptionEntry? encryption_entry = audio_encryption != null ? registry.call_encryption_entries[audio_encryption.encryption_ns] : null; + if (encryption_entry != null) { + Plugins.CallEncryptionWidget? encryption_widgets = encryption_entry.get_widget(call.account, audio_encryption); + if (encryption_widgets != null) { + title = encryption_widgets.get_title(); + icon_name = encryption_widgets.get_icon_name(); + show_keys = encryption_widgets.show_keys(); + } + } + call_window.bottom_bar.encryption_button.set_info(title, show_keys, audio_encryption, same ? null :video_encryption); + call_window.bottom_bar.encryption_button.set_icon(audio_encryption != null, icon_name); }); own_video.resolution_changed.connect((width, height) => { diff --git a/plugins/omemo/CMakeLists.txt b/plugins/omemo/CMakeLists.txt index 944fc649..195001cb 100644 --- a/plugins/omemo/CMakeLists.txt +++ b/plugins/omemo/CMakeLists.txt @@ -55,6 +55,7 @@ SOURCES src/ui/account_settings_entry.vala src/ui/account_settings_widget.vala src/ui/bad_messages_populator.vala + src/ui/call_encryption_entry.vala src/ui/contact_details_provider.vala src/ui/contact_details_dialog.vala src/ui/device_notification_populator.vala diff --git a/plugins/omemo/src/dtls_srtp_verification_draft.vala b/plugins/omemo/src/dtls_srtp_verification_draft.vala index 66a31954..5fc9b339 100644 --- a/plugins/omemo/src/dtls_srtp_verification_draft.vala +++ b/plugins/omemo/src/dtls_srtp_verification_draft.vala @@ -66,7 +66,7 @@ namespace Dino.Plugins.Omemo.DtlsSrtpVerificationDraft { stream.get_flag(Xep.Jingle.Flag.IDENTITY).get_session.begin(jingle_sid, (_, res) => { Xep.Jingle.Session? session = stream.get_flag(Xep.Jingle.Flag.IDENTITY).get_session.end(res); if (session == null || !session.contents_map.has_key(content_name)) return; - var encryption = new OmemoContentEncryption() { encryption_ns=NS_URI, encryption_name="OMEMO", our_key=new uint8[0], peer_key=new uint8[0], peer_device_id=device_id_by_jingle_sid[jingle_sid] }; + var encryption = new OmemoContentEncryption() { encryption_ns=NS_URI, encryption_name="OMEMO", our_key=new uint8[0], peer_key=new uint8[0], sid=device_id_by_jingle_sid[jingle_sid], jid=iq.from.bare_jid }; session.contents_map[content_name].encryptions[NS_URI] = encryption; if (iq.stanza.get_deep_attribute(Xep.Jingle.NS_URI + ":jingle", "action") == "session-accept") { @@ -143,7 +143,7 @@ namespace Dino.Plugins.Omemo.DtlsSrtpVerificationDraft { private void on_content_add_received(XmppStream stream, Xep.Jingle.Content content) { if (!content_names_by_jingle_sid.has_key(content.session.sid) || content_names_by_jingle_sid[content.session.sid].contains(content.content_name)) { - var encryption = new OmemoContentEncryption() { encryption_ns=NS_URI, encryption_name="OMEMO", our_key=new uint8[0], peer_key=new uint8[0], peer_device_id=device_id_by_jingle_sid[content.session.sid] }; + var encryption = new OmemoContentEncryption() { encryption_ns=NS_URI, encryption_name="OMEMO", our_key=new uint8[0], peer_key=new uint8[0], sid=device_id_by_jingle_sid[content.session.sid], jid=content.peer_full_jid.bare_jid }; content.encryptions[encryption.encryption_ns] = encryption; } } @@ -188,7 +188,8 @@ namespace Dino.Plugins.Omemo.DtlsSrtpVerificationDraft { } public class OmemoContentEncryption : Xep.Jingle.ContentEncryption { - public int peer_device_id { get; set; } + public Jid jid { get; set; } + public int sid { get; set; } } } diff --git a/plugins/omemo/src/logic/decrypt.vala b/plugins/omemo/src/logic/decrypt.vala index 3cdacbf7..cfbb9c58 100644 --- a/plugins/omemo/src/logic/decrypt.vala +++ b/plugins/omemo/src/logic/decrypt.vala @@ -59,9 +59,10 @@ namespace Dino.Plugins.Omemo { message.real_jid = possible_jid; } - trust_manager.message_device_id_map[message] = data.sid; message.body = cleartext; message.encryption = Encryption.OMEMO; + + trust_manager.message_device_id_map[message] = data.sid; return true; } catch (Error e) { debug("Decrypting message from %s/%d failed: %s", possible_jid.to_string(), data.sid, e.message); diff --git a/plugins/omemo/src/plugin.vala b/plugins/omemo/src/plugin.vala index 7a0304d1..643428a8 100644 --- a/plugins/omemo/src/plugin.vala +++ b/plugins/omemo/src/plugin.vala @@ -35,7 +35,6 @@ public class Plugin : RootInterface, Object { public DeviceNotificationPopulator device_notification_populator; public OwnNotifications own_notifications; public TrustManager trust_manager; - public DecryptMessageListener decrypt_message_listener; public HashMap decryptors = new HashMap(Account.hash_func, Account.equals_func); public HashMap encryptors = new HashMap(Account.hash_func, Account.equals_func); @@ -54,6 +53,7 @@ public class Plugin : RootInterface, Object { this.app.plugin_registry.register_contact_details_entry(contact_details_provider); this.app.plugin_registry.register_notification_populator(device_notification_populator); this.app.plugin_registry.register_conversation_addition_populator(new BadMessagesPopulator(this.app.stream_interactor, this)); + this.app.plugin_registry.register_call_entryption_entry(DtlsSrtpVerificationDraft.NS_URI, new CallEncryptionEntry(db)); this.app.stream_interactor.module_manager.initialize_account_modules.connect((account, list) => { Signal.Store signal_store = Plugin.get_context().create_store(); @@ -67,9 +67,7 @@ public class Plugin : RootInterface, Object { this.own_notifications = new OwnNotifications(this, this.app.stream_interactor, account); }); - decrypt_message_listener = new DecryptMessageListener(decryptors); - app.stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(decrypt_message_listener); - + app.stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(new DecryptMessageListener(decryptors)); app.stream_interactor.get_module(FileManager.IDENTITY).add_file_decryptor(new OmemoFileDecryptor()); app.stream_interactor.get_module(FileManager.IDENTITY).add_file_encryptor(new OmemoFileEncryptor()); JingleFileHelperRegistry.instance.add_encryption_helper(Encryption.OMEMO, new JetOmemo.EncryptionHelper(app.stream_interactor)); diff --git a/plugins/omemo/src/ui/call_encryption_entry.vala b/plugins/omemo/src/ui/call_encryption_entry.vala new file mode 100644 index 00000000..69b7b686 --- /dev/null +++ b/plugins/omemo/src/ui/call_encryption_entry.vala @@ -0,0 +1,57 @@ +using Dino.Entities; +using Gtk; +using Qlite; +using Xmpp; + +namespace Dino.Plugins.Omemo { + + public class CallEncryptionEntry : Plugins.CallEncryptionEntry, Object { + private Database db; + + public CallEncryptionEntry(Database db) { + this.db = db; + } + + public Plugins.CallEncryptionWidget? get_widget(Account account, Xmpp.Xep.Jingle.ContentEncryption encryption) { + DtlsSrtpVerificationDraft.OmemoContentEncryption? omemo_encryption = encryption as DtlsSrtpVerificationDraft.OmemoContentEncryption; + if (omemo_encryption == null) return null; + + int identity_id = db.identity.get_id(account.id); + Row? device = db.identity_meta.get_device(identity_id, omemo_encryption.jid.to_string(), omemo_encryption.sid); + if (device == null) return null; + TrustLevel trust = (TrustLevel) device[db.identity_meta.trust_level]; + + return new CallEncryptionWidget(trust); + } + } + + public class CallEncryptionWidget : Plugins.CallEncryptionWidget, Object { + + string? title = null; + string? icon = null; + bool should_show_keys = false; + + public CallEncryptionWidget(TrustLevel trust) { + if (trust == TrustLevel.VERIFIED) { + title = "This call is encrypted and verified with OMEMO."; + icon = "dino-security-high-symbolic"; + should_show_keys = false; + } else { + title = "This call is encrypted with OMEMO."; + should_show_keys = true; + } + } + + public string? get_title() { + return title; + } + + public string? get_icon_name() { + return icon; + } + + public bool show_keys() { + return should_show_keys; + } + } +} -- cgit v1.2.3-70-g09d2