Calls: Fix OMEMO in group calls

This commit is contained in:
Marvin W 2022-02-12 17:18:03 +01:00
parent 6f9375e6ea
commit b586aebbac
No known key found for this signature in database
GPG key ID: 072E9235DB996F2A
6 changed files with 88 additions and 24 deletions

View file

@ -143,23 +143,22 @@ namespace Dino {
} }
private void on_incoming_call(Account account, Xep.Jingle.Session session) { private void on_incoming_call(Account account, Xep.Jingle.Session session) {
Jid? muji_muc = null; Jid? muji_room = session.muji_room;
bool counterpart_wants_video = false; bool counterpart_wants_video = false;
foreach (Xep.Jingle.Content content in session.contents) { foreach (Xep.Jingle.Content content in session.contents) {
Xep.JingleRtp.Parameters? rtp_content_parameter = content.content_params as Xep.JingleRtp.Parameters; Xep.JingleRtp.Parameters? rtp_content_parameter = content.content_params as Xep.JingleRtp.Parameters;
if (rtp_content_parameter == null) continue; if (rtp_content_parameter == null) continue;
muji_muc = rtp_content_parameter.muji_muc;
if (rtp_content_parameter.media == "video" && session.senders_include_us(content.senders)) { if (rtp_content_parameter.media == "video" && session.senders_include_us(content.senders)) {
counterpart_wants_video = true; counterpart_wants_video = true;
} }
} }
// Check if this comes from a MUJI MUC => accept // Check if this comes from a MUJI MUC => accept
if (muji_muc != null) { if (muji_room != null) {
debug("[%s] Incoming call from %s from MUJI muc %s", account.bare_jid.to_string(), session.peer_full_jid.to_string(), muji_muc.to_string()); debug("[%s] Incoming call from %s from MUJI muc %s", account.bare_jid.to_string(), session.peer_full_jid.to_string(), muji_room.to_string());
foreach (CallState call_state in call_states.values) { foreach (CallState call_state in call_states.values) {
if (call_state.call.account.equals(account) && call_state.group_call != null && call_state.group_call.muc_jid.equals(muji_muc)) { if (call_state.call.account.equals(account) && call_state.group_call != null && call_state.group_call.muc_jid.equals(muji_room)) {
if (call_state.peers.keys.contains(session.peer_full_jid)) { if (call_state.peers.keys.contains(session.peer_full_jid)) {
PeerState peer_state = call_state.peers[session.peer_full_jid]; PeerState peer_state = call_state.peers[session.peer_full_jid];
debug("[%s] Incoming call, we know the peer. Expected %s", account.bare_jid.to_string(), peer_state.waiting_for_inbound_muji_connection.to_string()); debug("[%s] Incoming call, we know the peer. Expected %s", account.bare_jid.to_string(), peer_state.waiting_for_inbound_muji_connection.to_string());
@ -271,7 +270,7 @@ namespace Dino {
debug("[%s] Muji call received from %s for MUC %s, type %s", account.bare_jid.to_string(), inviter_jid.to_string(), muc_jid.to_string(), message_type); debug("[%s] Muji call received from %s for MUC %s, type %s", account.bare_jid.to_string(), inviter_jid.to_string(), muc_jid.to_string(), message_type);
foreach (Call call in call_states.keys) { foreach (Call call in call_states.keys) {
if (!call.account.equals(account)) return null; if (!call.account.equals(account)) continue;
CallState call_state = call_states[call]; CallState call_state = call_states[call];

View file

@ -11,6 +11,7 @@ namespace Dino.Plugins.Omemo.DtlsSrtpVerificationDraft {
private VerificationSendListener send_listener = new VerificationSendListener(); private VerificationSendListener send_listener = new VerificationSendListener();
private HashMap<string, int> device_id_by_jingle_sid = new HashMap<string, int>(); private HashMap<string, int> device_id_by_jingle_sid = new HashMap<string, int>();
private HashMap<string, int> device_id_by_muji_member = new HashMap<string, int>();
private HashMap<string, Gee.List<string>> content_names_by_jingle_sid = new HashMap<string, Gee.List<string>>(); private HashMap<string, Gee.List<string>> content_names_by_jingle_sid = new HashMap<string, Gee.List<string>>();
private void on_preprocess_incoming_iq_set_get(XmppStream stream, Xmpp.Iq.Stanza iq) { private void on_preprocess_incoming_iq_set_get(XmppStream stream, Xmpp.Iq.Stanza iq) {
@ -88,8 +89,26 @@ namespace Dino.Plugins.Omemo.DtlsSrtpVerificationDraft {
StanzaNode? jingle_node = iq.stanza.get_subnode("jingle", Xep.Jingle.NS_URI); StanzaNode? jingle_node = iq.stanza.get_subnode("jingle", Xep.Jingle.NS_URI);
if (jingle_node == null) return; if (jingle_node == null) return;
int device_id = -1;
string? sid = jingle_node.get_attribute("sid", Xep.Jingle.NS_URI); string? sid = jingle_node.get_attribute("sid", Xep.Jingle.NS_URI);
if (sid == null || !device_id_by_jingle_sid.has_key(sid)) return; if (sid != null && device_id_by_jingle_sid.has_key(sid)) {
device_id = device_id_by_jingle_sid[sid];
}
StanzaNode? muji_node = jingle_node.get_subnode("muji", Xep.Muji.NS_URI);
if (muji_node != null) {
string muji_room = muji_node.get_attribute("room");
try {
Jid muji_jid = new Jid(muji_room);
if (device_id_by_muji_member.has_key(@"$(muji_jid.bare_jid)/$(iq.to)")) {
device_id = device_id_by_muji_member[@"$(muji_jid.bare_jid)/$(iq.to)"];
}
} catch (InvalidJidError e) {
// Ignore
}
}
if (device_id == -1) return;
Gee.List<StanzaNode> content_nodes = jingle_node.get_subnodes("content", Xep.Jingle.NS_URI); Gee.List<StanzaNode> content_nodes = jingle_node.get_subnodes("content", Xep.Jingle.NS_URI);
if (content_nodes.size == 0) return; if (content_nodes.size == 0) return;
@ -105,7 +124,7 @@ namespace Dino.Plugins.Omemo.DtlsSrtpVerificationDraft {
try { try {
Xep.Omemo.OmemoEncryptor encryptor = stream.get_module(Xep.Omemo.OmemoEncryptor.IDENTITY); Xep.Omemo.OmemoEncryptor encryptor = stream.get_module(Xep.Omemo.OmemoEncryptor.IDENTITY);
Xep.Omemo.EncryptionData enc_data = encryptor.encrypt_plaintext(fingerprint); Xep.Omemo.EncryptionData enc_data = encryptor.encrypt_plaintext(fingerprint);
encryptor.encrypt_key(enc_data, iq.to.bare_jid, device_id_by_jingle_sid[sid]); encryptor.encrypt_key(enc_data, iq.to.bare_jid, device_id);
encrypted_node = enc_data.get_encrypted_node(); encrypted_node = enc_data.get_encrypted_node();
} catch (Error e) { } catch (Error e) {
warning("Error while OMEMO-encrypting call keys: %s", e.message); warning("Error while OMEMO-encrypting call keys: %s", e.message);
@ -155,12 +174,52 @@ namespace Dino.Plugins.Omemo.DtlsSrtpVerificationDraft {
} }
} }
private void on_pre_send_presence_stanza(XmppStream stream, Presence.Stanza presence) {
StanzaNode? muji_node = presence.stanza.get_subnode("muji", Xep.Muji.NS_URI);
if (muji_node == null) return;
StanzaNode device_node = new StanzaNode.build("device", NS_URI).add_self_xmlns()
.put_attribute("id", stream.get_module(Omemo.StreamModule.IDENTITY).store.local_registration_id.to_string());
muji_node.put_node(device_node);
}
private void on_received_available(XmppStream stream, Presence.Stanza presence) {
StanzaNode? muji_node = presence.stanza.get_subnode("muji", Xep.Muji.NS_URI);
if (muji_node == null) return;
StanzaNode? device_node = muji_node.get_subnode("device", NS_URI);
if (device_node == null) return;
int device_id = device_node.get_attribute_int("id", -1);
if (device_id == -1) return;
StanzaNode? muc_x_node = presence.stanza.get_subnode("x", "http://jabber.org/protocol/muc#user");
if (muc_x_node == null) return;
StanzaNode? item_node = muc_x_node.get_subnode("item");
if (item_node == null) return;
Jid? real_jid = null;
try {
string jid_attribute = item_node.get_attribute("jid");
if (jid_attribute == null) return;
real_jid = new Jid(jid_attribute);
} catch (InvalidJidError e) {
// Ignore
return;
}
device_id_by_muji_member[@"$(presence.from.bare_jid)/$(real_jid)"] = device_id;
}
public override void attach(XmppStream stream) { public override void attach(XmppStream stream) {
stream.get_module(Xmpp.MessageModule.IDENTITY).received_message.connect(on_message_received); stream.get_module(Xmpp.MessageModule.IDENTITY).received_message.connect(on_message_received);
stream.get_module(Xmpp.MessageModule.IDENTITY).send_pipeline.connect(send_listener); stream.get_module(Xmpp.MessageModule.IDENTITY).send_pipeline.connect(send_listener);
stream.get_module(Xmpp.Iq.Module.IDENTITY).preprocess_incoming_iq_set_get.connect(on_preprocess_incoming_iq_set_get); stream.get_module(Xmpp.Iq.Module.IDENTITY).preprocess_incoming_iq_set_get.connect(on_preprocess_incoming_iq_set_get);
stream.get_module(Xmpp.Iq.Module.IDENTITY).preprocess_outgoing_iq_set_get.connect(on_preprocess_outgoing_iq_set_get); stream.get_module(Xmpp.Iq.Module.IDENTITY).preprocess_outgoing_iq_set_get.connect(on_preprocess_outgoing_iq_set_get);
stream.get_module(Xep.Jingle.Module.IDENTITY).session_initiate_received.connect(on_session_initiate_received); stream.get_module(Xep.Jingle.Module.IDENTITY).session_initiate_received.connect(on_session_initiate_received);
stream.get_module(Xmpp.Presence.Module.IDENTITY).pre_send_presence_stanza.connect(on_pre_send_presence_stanza);
stream.get_module(Xmpp.Presence.Module.IDENTITY).received_available.connect(on_received_available);
} }
public override void detach(XmppStream stream) { public override void detach(XmppStream stream) {
@ -169,6 +228,7 @@ namespace Dino.Plugins.Omemo.DtlsSrtpVerificationDraft {
stream.get_module(Xmpp.Iq.Module.IDENTITY).preprocess_incoming_iq_set_get.disconnect(on_preprocess_incoming_iq_set_get); stream.get_module(Xmpp.Iq.Module.IDENTITY).preprocess_incoming_iq_set_get.disconnect(on_preprocess_incoming_iq_set_get);
stream.get_module(Xmpp.Iq.Module.IDENTITY).preprocess_outgoing_iq_set_get.disconnect(on_preprocess_outgoing_iq_set_get); stream.get_module(Xmpp.Iq.Module.IDENTITY).preprocess_outgoing_iq_set_get.disconnect(on_preprocess_outgoing_iq_set_get);
stream.get_module(Xep.Jingle.Module.IDENTITY).session_initiate_received.disconnect(on_session_initiate_received); stream.get_module(Xep.Jingle.Module.IDENTITY).session_initiate_received.disconnect(on_session_initiate_received);
stream.get_module(Xmpp.Presence.Module.IDENTITY).received_available.disconnect(on_received_available);
} }
public override string get_ns() { return NS_URI; } public override string get_ns() { return NS_URI; }

View file

@ -102,7 +102,7 @@ namespace Xmpp.Xep.Jingle {
return (yield is_jingle_available(stream, full_jid)) && (yield select_transport(stream, type, components, full_jid, Set.empty())) != null; return (yield is_jingle_available(stream, full_jid)) && (yield select_transport(stream, type, components, full_jid, Set.empty())) != null;
} }
public async Session create_session(XmppStream stream, Gee.List<Content> contents, Jid receiver_full_jid, string? sid = null) throws Error { public async Session create_session(XmppStream stream, Gee.List<Content> contents, Jid receiver_full_jid, string? sid = null, Jid? muji_room = null) throws Error {
if (!yield is_jingle_available(stream, receiver_full_jid)) { if (!yield is_jingle_available(stream, receiver_full_jid)) {
throw new Error.NO_SHARED_PROTOCOLS("No Jingle support"); throw new Error.NO_SHARED_PROTOCOLS("No Jingle support");
} }
@ -138,6 +138,10 @@ namespace Xmpp.Xep.Jingle {
initiate_jingle_iq.put_node(content_node); initiate_jingle_iq.put_node(content_node);
} }
if (muji_room != null) {
initiate_jingle_iq.put_node(new StanzaNode.build("muji", Xep.Muji.NS_URI).add_self_xmlns().put_attribute("room", muji_room.to_string()));
}
Iq.Stanza iq = new Iq.Stanza.set(initiate_jingle_iq) { to=receiver_full_jid }; Iq.Stanza iq = new Iq.Stanza.set(initiate_jingle_iq) { to=receiver_full_jid };
stream.get_flag(Flag.IDENTITY).add_session(session); stream.get_flag(Flag.IDENTITY).add_session(session);
@ -158,6 +162,15 @@ namespace Xmpp.Xep.Jingle {
Session session = new Session.initiate_received(stream, sid, my_jid, iq.from); Session session = new Session.initiate_received(stream, sid, my_jid, iq.from);
session.terminated.connect((stream) => { stream.get_flag(Flag.IDENTITY).remove_session(sid); }); session.terminated.connect((stream) => { stream.get_flag(Flag.IDENTITY).remove_session(sid); });
string? muji_room_str = iq.stanza.get_deep_attribute(NS_URI + ":jingle", Xep.Muji.NS_URI + ":muji", "room");
if (muji_room_str != null) {
try {
session.muji_room = new Jid(muji_room_str);
} catch (InvalidJidError e) {
// Ignore
}
}
stream.get_flag(Flag.IDENTITY).pre_add_session(session.sid); stream.get_flag(Flag.IDENTITY).pre_add_session(session.sid);
foreach (ContentNode content_node in get_content_nodes(jingle)) { foreach (ContentNode content_node in get_content_nodes(jingle)) {

View file

@ -29,6 +29,8 @@ public class Xmpp.Xep.Jingle.Session : Object {
public SecurityParameters? security { get { return contents.to_array()[0].security_params; } } public SecurityParameters? security { get { return contents.to_array()[0].security_params; } }
public Jid muji_room { get; set; }
public Session.initiate_sent(XmppStream stream, string sid, Jid local_full_jid, Jid peer_full_jid) { public Session.initiate_sent(XmppStream stream, string sid, Jid local_full_jid, Jid peer_full_jid) {
this.stream = stream; this.stream = stream;
this.sid = sid; this.sid = sid;

View file

@ -21,7 +21,6 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object {
public Gee.List<Crypto> remote_cryptos = new ArrayList<Crypto>(); public Gee.List<Crypto> remote_cryptos = new ArrayList<Crypto>();
public Crypto? local_crypto = null; public Crypto? local_crypto = null;
public Crypto? remote_crypto = null; public Crypto? remote_crypto = null;
public Jid? muji_muc = null;
public bool rtp_ready { get; private set; default=false; } public bool rtp_ready { get; private set; default=false; }
public bool rtcp_ready { get; private set; default=false; } public bool rtcp_ready { get; private set; default=false; }
@ -32,7 +31,6 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object {
public Parameters(Module parent, public Parameters(Module parent,
string media, Gee.List<PayloadType> payload_types, string media, Gee.List<PayloadType> payload_types,
Jid? muji_muc,
string? ssrc = null, bool rtcp_mux = false, string? ssrc = null, bool rtcp_mux = false,
string? bandwidth = null, string? bandwidth_type = null, string? bandwidth = null, string? bandwidth_type = null,
bool encryption_required = false, Crypto? local_crypto = null bool encryption_required = false, Crypto? local_crypto = null
@ -46,7 +44,6 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object {
this.encryption_required = encryption_required; this.encryption_required = encryption_required;
this.payload_types = payload_types; this.payload_types = payload_types;
this.local_crypto = local_crypto; this.local_crypto = local_crypto;
this.muji_muc = muji_muc;
} }
public Parameters.from_node(Module parent, StanzaNode node) throws Jingle.IqError { public Parameters.from_node(Module parent, StanzaNode node) throws Jingle.IqError {
@ -67,10 +64,6 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object {
foreach (StanzaNode subnode in node.get_subnodes(HeaderExtension.NAME, HeaderExtension.NS_URI)) { foreach (StanzaNode subnode in node.get_subnodes(HeaderExtension.NAME, HeaderExtension.NS_URI)) {
this.header_extensions.add(HeaderExtension.parse(subnode)); this.header_extensions.add(HeaderExtension.parse(subnode));
} }
string? muji_muc_str = node.get_deep_attribute(Xep.Muji.NS_URI + ":muji", "muc");
if (muji_muc_str != null) {
muji_muc = new Jid(muji_muc_str);
}
} }
public async void handle_proposed_content(XmppStream stream, Jingle.Session session, Jingle.Content content) { public async void handle_proposed_content(XmppStream stream, Jingle.Session session, Jingle.Content content) {
@ -216,9 +209,6 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object {
if (rtcp_mux) { if (rtcp_mux) {
ret.put_node(new StanzaNode.build("rtcp-mux", NS_URI)); ret.put_node(new StanzaNode.build("rtcp-mux", NS_URI));
} }
if (muji_muc != null) {
ret.put_node(new StanzaNode.build("muji", Xep.Muji.NS_URI).add_self_xmlns().put_attribute("muc", muji_muc.to_string()));
}
return ret; return ret;
} }
} }

View file

@ -29,7 +29,7 @@ public abstract class Module : XmppStreamModule {
public abstract Gee.List<HeaderExtension> get_suggested_header_extensions(string media); public abstract Gee.List<HeaderExtension> get_suggested_header_extensions(string media);
public abstract void close_stream(Stream stream); public abstract void close_stream(Stream stream);
public async Jingle.Session start_call(XmppStream stream, Jid receiver_full_jid, bool video, string sid, Jid? muji_muc) throws Jingle.Error { public async Jingle.Session start_call(XmppStream stream, Jid receiver_full_jid, bool video, string sid, Jid? muji_room) throws Jingle.Error {
Jingle.Module jingle_module = stream.get_module(Jingle.Module.IDENTITY); Jingle.Module jingle_module = stream.get_module(Jingle.Module.IDENTITY);
@ -41,7 +41,7 @@ public abstract class Module : XmppStreamModule {
ArrayList<Jingle.Content> contents = new ArrayList<Jingle.Content>(); ArrayList<Jingle.Content> contents = new ArrayList<Jingle.Content>();
// Create audio content // Create audio content
Parameters audio_content_parameters = new Parameters(this, "audio", yield get_supported_payloads("audio"), muji_muc); Parameters audio_content_parameters = new Parameters(this, "audio", yield get_supported_payloads("audio"));
audio_content_parameters.local_crypto = generate_local_crypto(); audio_content_parameters.local_crypto = generate_local_crypto();
audio_content_parameters.header_extensions.add_all(get_suggested_header_extensions("audio")); audio_content_parameters.header_extensions.add_all(get_suggested_header_extensions("audio"));
Jingle.Transport? audio_transport = yield jingle_module.select_transport(stream, content_type.required_transport_type, content_type.required_components, receiver_full_jid, Set.empty()); Jingle.Transport? audio_transport = yield jingle_module.select_transport(stream, content_type.required_transport_type, content_type.required_components, receiver_full_jid, Set.empty());
@ -59,7 +59,7 @@ public abstract class Module : XmppStreamModule {
Jingle.Content? video_content = null; Jingle.Content? video_content = null;
if (video) { if (video) {
// Create video content // Create video content
Parameters video_content_parameters = new Parameters(this, "video", yield get_supported_payloads("video"), muji_muc); Parameters video_content_parameters = new Parameters(this, "video", yield get_supported_payloads("video"));
video_content_parameters.local_crypto = generate_local_crypto(); video_content_parameters.local_crypto = generate_local_crypto();
video_content_parameters.header_extensions.add_all(get_suggested_header_extensions("video")); video_content_parameters.header_extensions.add_all(get_suggested_header_extensions("video"));
Jingle.Transport? video_transport = yield stream.get_module(Jingle.Module.IDENTITY).select_transport(stream, content_type.required_transport_type, content_type.required_components, receiver_full_jid, Set.empty()); Jingle.Transport? video_transport = yield stream.get_module(Jingle.Module.IDENTITY).select_transport(stream, content_type.required_transport_type, content_type.required_components, receiver_full_jid, Set.empty());
@ -77,7 +77,7 @@ public abstract class Module : XmppStreamModule {
// Create session // Create session
try { try {
Jingle.Session session = yield jingle_module.create_session(stream, contents, receiver_full_jid, sid); Jingle.Session session = yield jingle_module.create_session(stream, contents, receiver_full_jid, sid, muji_room);
return session; return session;
} catch (Jingle.Error e) { } catch (Jingle.Error e) {
throw new Jingle.Error.GENERAL(@"Couldn't create Jingle session: $(e.message)"); throw new Jingle.Error.GENERAL(@"Couldn't create Jingle session: $(e.message)");
@ -101,7 +101,7 @@ public abstract class Module : XmppStreamModule {
if (content == null) { if (content == null) {
// Content for video does not yet exist -> create it // Content for video does not yet exist -> create it
Parameters video_content_parameters = new Parameters(this, "video", yield get_supported_payloads("video"), muji_muc); Parameters video_content_parameters = new Parameters(this, "video", yield get_supported_payloads("video"));
video_content_parameters.local_crypto = generate_local_crypto(); video_content_parameters.local_crypto = generate_local_crypto();
video_content_parameters.header_extensions.add_all(get_suggested_header_extensions("video")); video_content_parameters.header_extensions.add_all(get_suggested_header_extensions("video"));
Jingle.Transport? video_transport = yield stream.get_module(Jingle.Module.IDENTITY).select_transport(stream, content_type.required_transport_type, content_type.required_components, receiver_full_jid, Set.empty()); Jingle.Transport? video_transport = yield stream.get_module(Jingle.Module.IDENTITY).select_transport(stream, content_type.required_transport_type, content_type.required_components, receiver_full_jid, Set.empty());