Calls: Indicate whether OMEMO key is verified
This commit is contained in:
parent
8044b546d0
commit
90f9ecf62b
|
@ -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; } }
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Dino.Plugins {
|
|||
|
||||
public class Registry {
|
||||
internal ArrayList<EncryptionListEntry> encryption_list_entries = new ArrayList<EncryptionListEntry>();
|
||||
internal HashMap<string, CallEncryptionEntry> call_encryption_entries = new HashMap<string, CallEncryptionEntry>();
|
||||
internal ArrayList<AccountSettingsEntry> account_settings_entries = new ArrayList<AccountSettingsEntry>();
|
||||
internal ArrayList<ContactDetailsProvider> contact_details_entries = new ArrayList<ContactDetailsProvider>();
|
||||
internal Map<string, TextCommand> text_commands = new HashMap<string, TextCommand>();
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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("<b>This call is encrypted with OMEMO.</b>") { use_markup=true, xalign=0, visible=true } );
|
||||
} else {
|
||||
box.add(new Label("<b>This call is end-to-end encrypted.</b>") { use_markup=true, xalign=0, visible=true });
|
||||
}
|
||||
|
||||
if (same) {
|
||||
box.add(create_media_encryption_grid(audio_encryption));
|
||||
} else {
|
||||
box.add(new Label("<b>Audio</b>") { use_markup=true, xalign=0, visible=true });
|
||||
box.add(create_media_encryption_grid(audio_encryption));
|
||||
box.add(new Label("<b>Video</b>") { 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("<span font_family='monospace'>" + format_fingerprint(encryption.peer_key) + "</span>") { 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("<span font_family='monospace'>" + format_fingerprint(encryption.our_key) + "</span>") { 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;
|
||||
}
|
||||
}
|
77
main/src/ui/call_window/call_encryption_button.vala
Normal file
77
main/src/ui/call_window/call_encryption_button.vala
Normal file
|
@ -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("<b>%s</b>".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("<b>Audio</b>") { use_markup=true, xalign=0, visible=true });
|
||||
box.add(create_media_encryption_grid(audio_encryption));
|
||||
box.add(new Label("<b>Video</b>") { 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("<span font_family='monospace'>" + format_fingerprint(encryption.peer_key) + "</span>") { 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("<span font_family='monospace'>" + format_fingerprint(encryption.our_key) + "</span>") { 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;
|
||||
}
|
||||
}
|
|
@ -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) => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<Account, OmemoDecryptor> decryptors = new HashMap<Account, OmemoDecryptor>(Account.hash_func, Account.equals_func);
|
||||
public HashMap<Account, OmemoEncryptor> encryptors = new HashMap<Account, OmemoEncryptor>(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));
|
||||
|
|
57
plugins/omemo/src/ui/call_encryption_entry.vala
Normal file
57
plugins/omemo/src/ui/call_encryption_entry.vala
Normal file
|
@ -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 <b>encrypted and verified</b> 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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue