From 0ad968df367f5a44c568329834115018866ff8b9 Mon Sep 17 00:00:00 2001 From: fiaxh Date: Fri, 30 Apr 2021 21:37:02 +0200 Subject: [PATCH] Use the same DTLS fingerprint in all contents. Display audio+video enc keys in UI if they differ. --- libdino/src/service/calls.vala | 22 +++++-- main/src/ui/call_window/call_bottom_bar.vala | 60 +++++++++++-------- .../call_window/call_window_controller.vala | 4 +- plugins/ice/src/dtls_srtp.vala | 35 +++++++---- plugins/ice/src/module.vala | 17 +++++- plugins/ice/src/transport_parameters.vala | 8 +-- 6 files changed, 96 insertions(+), 50 deletions(-) diff --git a/libdino/src/service/calls.vala b/libdino/src/service/calls.vala index d535dfca..a44b59fd 100644 --- a/libdino/src/service/calls.vala +++ b/libdino/src/service/calls.vala @@ -14,7 +14,7 @@ namespace Dino { public signal void counterpart_ringing(Call call); public signal void counterpart_sends_video_updated(Call call, bool mute); public signal void info_received(Call call, Xep.JingleRtp.CallSessionInfo session_info); - public signal void encryption_updated(Call call, Xep.Jingle.ContentEncryption? encryption); + public signal void encryption_updated(Call call, Xep.Jingle.ContentEncryption? audio_encryption, Xep.Jingle.ContentEncryption? video_encryption, bool same); public signal void stream_created(Call call, string media); @@ -523,7 +523,7 @@ namespace Dino { if ((audio_encryptions.has_key(call) && audio_encryptions[call].is_empty) || (video_encryptions.has_key(call) && video_encryptions[call].is_empty)) { call.encryption = Encryption.NONE; - encryption_updated(call, null); + encryption_updated(call, null, null, true); return; } @@ -545,16 +545,26 @@ namespace Dino { if (omemo_encryption != null && dtls_encryption != null) { call.encryption = Encryption.OMEMO; - encryption_updated(call, omemo_encryption); + Xep.Jingle.ContentEncryption? video_encryption = video_encryptions.has_key(call) ? video_encryptions[call]["http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification"] : null; + omemo_encryption.peer_key = dtls_encryption.peer_key; + omemo_encryption.our_key = dtls_encryption.our_key; + encryption_updated(call, omemo_encryption, video_encryption, true); } else if (dtls_encryption != null) { call.encryption = Encryption.DTLS_SRTP; - encryption_updated(call, dtls_encryption); + Xep.Jingle.ContentEncryption? video_encryption = video_encryptions.has_key(call) ? video_encryptions[call][Xep.JingleIceUdp.DTLS_NS_URI] : null; + bool same = true; + if (video_encryption != null && dtls_encryption.peer_key.length == video_encryption.peer_key.length) { + for (int i = 0; i < dtls_encryption.peer_key.length; i++) { + if (dtls_encryption.peer_key[i] != video_encryption.peer_key[i]) { same = false; break; } + } + } + encryption_updated(call, dtls_encryption, video_encryption, same); } else if (srtp_encryption != null) { call.encryption = Encryption.SRTP; - encryption_updated(call, srtp_encryption); + encryption_updated(call, srtp_encryption, video_encryptions[call]["SRTP"], false); } else { call.encryption = Encryption.NONE; - encryption_updated(call, null); + encryption_updated(call, null, null, true); } } diff --git a/main/src/ui/call_window/call_bottom_bar.vala b/main/src/ui/call_window/call_bottom_bar.vala index a3e4b93b..64b157dd 100644 --- a/main/src/ui/call_window/call_bottom_bar.vala +++ b/main/src/ui/call_window/call_bottom_bar.vala @@ -89,42 +89,54 @@ public class Dino.Ui.CallBottomBar : Gtk.Box { this.get_style_context().add_class("call-bottom-bar"); } - public void set_encryption(Xmpp.Xep.Jingle.ContentEncryption? encryption) { + 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 (encryption == null) { + 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 } ); - } else if (encryption.encryption_name == "OMEMO") { - encryption_image.set_from_icon_name("changes-prevent-symbolic", IconSize.BUTTON); - encryption_button.get_style_context().remove_class("unencrypted"); - - popover.add(new Label("This call is encrypted with OMEMO.") { margin=10, visible=true } ); - } else { - encryption_image.set_from_icon_name("changes-prevent-symbolic", IconSize.BUTTON); - encryption_button.get_style_context().remove_class("unencrypted"); - - Grid encryption_info_grid = new Grid() { margin=10, row_spacing=3, column_spacing=5, visible=true }; - encryption_info_grid.attach(new Label("This call is end-to-end encrypted.") { use_markup=true, xalign=0, visible=true }, 1, 1, 2, 1); - if (encryption.peer_key.length > 0) { - encryption_info_grid.attach(new Label("Peer key") { xalign=0, visible=true }, 1, 2, 1, 1); - encryption_info_grid.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) { - encryption_info_grid.attach(new Label("Your key") { xalign=0, visible=true }, 1, 3, 1, 1); - encryption_info_grid.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); - } - - popover.add(encryption_info_grid); + 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; diff --git a/main/src/ui/call_window/call_window_controller.vala b/main/src/ui/call_window/call_window_controller.vala index 0a223d72..7e5920ce 100644 --- a/main/src/ui/call_window/call_window_controller.vala +++ b/main/src/ui/call_window/call_window_controller.vala @@ -76,9 +76,9 @@ public class Dino.Ui.CallWindowController : Object { call_window.set_status("ringing"); } }); - calls.encryption_updated.connect((call, encryption) => { + calls.encryption_updated.connect((call, audio_encryption, video_encryption, same) => { if (!this.call.equals(call)) return; - call_window.bottom_bar.set_encryption(encryption); + call_window.bottom_bar.set_encryption(audio_encryption, video_encryption, same); }); own_video.resolution_changed.connect((width, height) => { diff --git a/plugins/ice/src/dtls_srtp.vala b/plugins/ice/src/dtls_srtp.vala index f5ef830a..0254351d 100644 --- a/plugins/ice/src/dtls_srtp.vala +++ b/plugins/ice/src/dtls_srtp.vala @@ -2,10 +2,10 @@ using GnuTLS; namespace Dino.Plugins.Ice.DtlsSrtp { -public static Handler setup() throws GLib.Error { - var obj = new Handler(); - obj.generate_credentials(); - return obj; +public class CredentialsCapsule { + public uint8[] own_fingerprint; + public X509.Certificate[] own_cert; + public X509.PrivateKey private_key; } public class Handler { @@ -21,8 +21,7 @@ public class Handler { public uint8[] peer_fingerprint { get; set; } public string peer_fp_algo { get; set; } - private X509.Certificate[] own_cert; - private X509.PrivateKey private_key; + private CredentialsCapsule credentials; private Cond buffer_cond = Cond(); private Mutex buffer_mutex = Mutex(); private Gee.LinkedList buffer_queue = new Gee.LinkedList(); @@ -33,6 +32,11 @@ public class Handler { private Crypto.Srtp.Session srtp_session = new Crypto.Srtp.Session(); + public Handler.with_cert(CredentialsCapsule creds) { + this.credentials = creds; + this.own_fingerprint = creds.own_fingerprint; + } + public uint8[]? process_incoming_data(uint component_id, uint8[] data) { if (srtp_session.has_decrypt) { try { @@ -78,10 +82,10 @@ public class Handler { buffer_mutex.unlock(); } - internal void generate_credentials() throws GLib.Error { + internal static CredentialsCapsule generate_credentials() throws GLib.Error { int err = 0; - private_key = X509.PrivateKey.create(); + X509.PrivateKey private_key = X509.PrivateKey.create(); err = private_key.generate(PKAlgorithm.RSA, 2048); throw_if_error(err); @@ -99,8 +103,15 @@ public class Handler { cert.sign(cert, private_key); - own_fingerprint = get_fingerprint(cert, DigestAlgorithm.SHA256); - own_cert = new X509.Certificate[] { (owned)cert }; + uint8[] own_fingerprint = get_fingerprint(cert, DigestAlgorithm.SHA256); + X509.Certificate[] own_cert = new X509.Certificate[] { (owned)cert }; + + var creds = new CredentialsCapsule(); + creds.own_fingerprint = own_fingerprint; + creds.own_cert = (owned) own_cert; + creds.private_key = (owned) private_key; + + return creds; } public void stop_dtls_connection() { @@ -129,7 +140,7 @@ public class Handler { debug("Setting up DTLS connection. We're %s", mode.to_string()); CertificateCredentials cert_cred = CertificateCredentials.create(); - int err = cert_cred.set_x509_key(own_cert, private_key); + int err = cert_cred.set_x509_key(credentials.own_cert, credentials.private_key); throw_if_error(err); Session? session = Session.create(server_or_client | InitFlags.DATAGRAM); @@ -200,7 +211,7 @@ public class Handler { srtp_session.set_encryption_key(Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, client_key.extract(), client_salt.extract()); srtp_session.set_decryption_key(Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, server_key.extract(), server_salt.extract()); } - return new Xmpp.Xep.Jingle.ContentEncryption() { encryption_ns=Xmpp.Xep.JingleIceUdp.DTLS_NS_URI, encryption_name = "DTLS-SRTP", our_key=own_fingerprint, peer_key=peer_fingerprint }; + return new Xmpp.Xep.Jingle.ContentEncryption() { encryption_ns=Xmpp.Xep.JingleIceUdp.DTLS_NS_URI, encryption_name = "DTLS-SRTP", our_key=credentials.own_fingerprint, peer_key=peer_fingerprint }; } private static ssize_t pull_function(void* transport_ptr, uint8[] buffer) { diff --git a/plugins/ice/src/module.vala b/plugins/ice/src/module.vala index e961ffb6..2645d7dc 100644 --- a/plugins/ice/src/module.vala +++ b/plugins/ice/src/module.vala @@ -10,6 +10,7 @@ public class Dino.Plugins.Ice.Module : JingleIceUdp.Module { public Xep.ExternalServiceDiscovery.Service? turn_service = null; private weak Nice.Agent? agent; + private HashMap cerds = new HashMap(); private Nice.Agent get_agent() { Nice.Agent? agent = this.agent; @@ -29,11 +30,23 @@ public class Dino.Plugins.Ice.Module : JingleIceUdp.Module { } public override Jingle.TransportParameters create_transport_parameters(XmppStream stream, uint8 components, Jid local_full_jid, Jid peer_full_jid) { - return new TransportParameters(get_agent(), turn_service, turn_ip, components, local_full_jid, peer_full_jid); + DtlsSrtp.CredentialsCapsule? cred = get_create_credentials(local_full_jid, peer_full_jid); + return new TransportParameters(get_agent(), cred, turn_service, turn_ip, components, local_full_jid, peer_full_jid); } public override Jingle.TransportParameters parse_transport_parameters(XmppStream stream, uint8 components, Jid local_full_jid, Jid peer_full_jid, StanzaNode transport) throws Jingle.IqError { - return new TransportParameters(get_agent(), turn_service, turn_ip, components, local_full_jid, peer_full_jid, transport); + DtlsSrtp.CredentialsCapsule? cred = get_create_credentials(local_full_jid, peer_full_jid); + return new TransportParameters(get_agent(), cred, turn_service, turn_ip, components, local_full_jid, peer_full_jid, transport); + } + + private DtlsSrtp.CredentialsCapsule? get_create_credentials(Jid local_full_jid, Jid peer_full_jid) { + string from_to_id = local_full_jid.to_string() + peer_full_jid.to_string(); + try { + if (!cerds.has_key(from_to_id)) cerds[from_to_id] = DtlsSrtp.Handler.generate_credentials(); + } catch (Error e) { + warning("Error creating dtls credentials: %s", e.message); + } + return cerds[from_to_id]; } private void agent_unweak() { diff --git a/plugins/ice/src/transport_parameters.vala b/plugins/ice/src/transport_parameters.vala index 38652952..62c04906 100644 --- a/plugins/ice/src/transport_parameters.vala +++ b/plugins/ice/src/transport_parameters.vala @@ -60,13 +60,13 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport } } - public TransportParameters(Nice.Agent agent, Xep.ExternalServiceDiscovery.Service? turn_service, string? turn_ip, uint8 components, Jid local_full_jid, Jid peer_full_jid, StanzaNode? node = null) { + public TransportParameters(Nice.Agent agent, DtlsSrtp.CredentialsCapsule? credentials, Xep.ExternalServiceDiscovery.Service? turn_service, string? turn_ip, uint8 components, Jid local_full_jid, Jid peer_full_jid, StanzaNode? node = null) { base(components, local_full_jid, peer_full_jid, node); this.we_want_connection = (node == null); this.agent = agent; if (this.peer_fingerprint != null || !incoming) { - dtls_srtp_handler = setup_dtls(this); + dtls_srtp_handler = setup_dtls(this, credentials); own_fingerprint = dtls_srtp_handler.own_fingerprint; if (incoming) { own_setup = "active"; @@ -113,9 +113,9 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport agent.gather_candidates(stream_id); } - private static DtlsSrtp.Handler setup_dtls(TransportParameters tp) { + private static DtlsSrtp.Handler setup_dtls(TransportParameters tp, DtlsSrtp.CredentialsCapsule credentials) { var weak_self = WeakRef(tp); - DtlsSrtp.Handler dtls_srtp = DtlsSrtp.setup(); + DtlsSrtp.Handler dtls_srtp = new DtlsSrtp.Handler.with_cert(credentials); dtls_srtp.send_data.connect((data) => { TransportParameters self = (TransportParameters) weak_self.get(); if (self != null) self.agent.send(self.stream_id, 1, data);