From fbc10c2023a4c2b874f87940f0a71bc0d8d7b57d Mon Sep 17 00:00:00 2001 From: Marvin W Date: Fri, 9 Apr 2021 22:23:53 +0200 Subject: DTLS-SRTP: Wait for setup finish and handle setup=passive --- plugins/crypto-vala/src/srtp.vala | 4 +- plugins/ice/src/dtls_srtp.vala | 162 ++++++++++++++------- plugins/ice/src/transport_parameters.vala | 65 +++++---- .../xep/0167_jingle_rtp/content_parameters.vala | 3 + .../0176_jingle_ice_udp/transport_parameters.vala | 16 +- 5 files changed, 159 insertions(+), 91 deletions(-) diff --git a/plugins/crypto-vala/src/srtp.vala b/plugins/crypto-vala/src/srtp.vala index 77b5acde..493afdb0 100644 --- a/plugins/crypto-vala/src/srtp.vala +++ b/plugins/crypto-vala/src/srtp.vala @@ -6,8 +6,8 @@ public class Crypto.Srtp { public const string F8_128_HMAC_SHA1_80 = "F8_128_HMAC_SHA1_80"; public class Session { - public bool has_encrypt { get; private set; } - public bool has_decrypt { get; private set; } + public bool has_encrypt { get; private set; default = false; } + public bool has_decrypt { get; private set; default = false; } private Context encrypt_context; private Context decrypt_context; diff --git a/plugins/ice/src/dtls_srtp.vala b/plugins/ice/src/dtls_srtp.vala index e2470cf6..8a9b5dfa 100644 --- a/plugins/ice/src/dtls_srtp.vala +++ b/plugins/ice/src/dtls_srtp.vala @@ -1,9 +1,26 @@ using GnuTLS; -public class DtlsSrtp { +namespace Dino.Plugins.Ice.DtlsSrtp { + +public static Handler setup() throws GLib.Error { + var obj = new Handler(); + obj.generate_credentials(); + return obj; +} + +public class Handler { public signal void send_data(uint8[] data); + public bool ready { get { + return srtp_session.has_encrypt && srtp_session.has_decrypt; + }} + + public Mode mode { get; set; default = Mode.CLIENT; } + public uint8[] own_fingerprint { get; private set; } + public uint8[] peer_fingerprint { get; set; } + public string peer_fp_algo { get; set; } + private X509.Certificate[] own_cert; private X509.PrivateKey private_key; private Cond buffer_cond = new Cond(); @@ -11,27 +28,12 @@ public class DtlsSrtp { private Gee.LinkedList buffer_queue = new Gee.LinkedList(); private uint pull_timeout = uint.MAX; - private DigestAlgorithm? peer_fp_algo = null; - private uint8[] peer_fingerprint = null; - private uint8[] own_fingerprint; + private bool running = false; + private bool stop = false; + private bool restart = false; private Crypto.Srtp.Session srtp_session = new Crypto.Srtp.Session(); - public static DtlsSrtp setup() throws GLib.Error { - var obj = new DtlsSrtp(); - obj.generate_credentials(); - return obj; - } - - internal uint8[] get_own_fingerprint(DigestAlgorithm digest_algo) { - return own_fingerprint; - } - - public void set_peer_fingerprint(uint8[] fingerprint, DigestAlgorithm digest_algo) { - this.peer_fingerprint = fingerprint; - this.peer_fp_algo = digest_algo; - } - public uint8[] process_incoming_data(uint component_id, uint8[] data) { if (srtp_session.has_decrypt) { try { @@ -77,7 +79,7 @@ public class DtlsSrtp { buffer_mutex.unlock(); } - private void generate_credentials() throws GLib.Error { + internal void generate_credentials() throws GLib.Error { int err = 0; private_key = X509.PrivateKey.create(); @@ -102,8 +104,29 @@ public class DtlsSrtp { own_cert = new X509.Certificate[] { (owned)cert }; } - public async Xmpp.Xep.Jingle.ContentEncryption setup_dtls_connection(bool server) { - InitFlags server_or_client = server ? InitFlags.SERVER : InitFlags.CLIENT; + public void stop_dtls_connection() { + buffer_mutex.lock(); + stop = true; + buffer_cond.signal(); + buffer_mutex.unlock(); + } + + public async Xmpp.Xep.Jingle.ContentEncryption? setup_dtls_connection() { + buffer_mutex.lock(); + if (stop) { + restart = true; + buffer_mutex.unlock(); + return null; + } + if (running || ready) { + buffer_mutex.unlock(); + return null; + } + running = true; + restart = false; + buffer_mutex.unlock(); + + InitFlags server_or_client = mode == Mode.SERVER ? InitFlags.SERVER : InitFlags.CLIENT; debug("Setting up DTLS connection. We're %s", server_or_client.to_string()); CertificateCredentials cert_cred = CertificateCredentials.create(); @@ -131,7 +154,7 @@ public class DtlsSrtp { DateTime current_time = new DateTime.now_utc(); if (maximum_time.compare(current_time) < 0) { warning("DTLS handshake timeouted"); - return -1; + return ErrorCode.APPLICATION_ERROR_MIN + 1; } } while (err < 0 && !((ErrorCode)err).is_fatal()); Idle.add(setup_dtls_connection.callback); @@ -139,6 +162,17 @@ public class DtlsSrtp { }); yield; err = thread.join(); + buffer_mutex.lock(); + if (stop) { + stop = false; + running = false; + bool restart = restart; + buffer_mutex.unlock(); + if (restart) return yield setup_dtls_connection(); + return null; + } + buffer_mutex.unlock(); + throw_if_error(err); uint8[] km = new uint8[150]; Datum? client_key, client_salt, server_key, server_salt; @@ -147,7 +181,8 @@ public class DtlsSrtp { warning("SRTP client/server key/salt null"); } - if (server) { + debug("Finished DTLS connection. We're %s", server_or_client.to_string()); + if (mode == Mode.SERVER) { srtp_session.set_encryption_key(Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, server_key.extract(), server_salt.extract()); srtp_session.set_decryption_key(Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, client_key.extract(), client_salt.extract()); } else { @@ -158,24 +193,28 @@ public class DtlsSrtp { } private static ssize_t pull_function(void* transport_ptr, uint8[] buffer) { - DtlsSrtp self = transport_ptr as DtlsSrtp; + Handler self = transport_ptr as Handler; self.buffer_mutex.lock(); while (self.buffer_queue.size == 0) { self.buffer_cond.wait(self.buffer_mutex); + if (self.stop) { + self.buffer_mutex.unlock(); + return -1; + } } - owned Bytes data = self.buffer_queue.remove_at(0); + Bytes data = self.buffer_queue.remove_at(0); self.buffer_mutex.unlock(); - uint8[] data_uint8 = Bytes.unref_to_data(data); + uint8[] data_uint8 = Bytes.unref_to_data((owned) data); Memory.copy(buffer, data_uint8, data_uint8.length); // The callback should return 0 on connection termination, a positive number indicating the number of bytes received, and -1 on error. - return (ssize_t)data.length; + return (ssize_t)data_uint8.length; } private static int pull_timeout_function(void* transport_ptr, uint ms) { - DtlsSrtp self = transport_ptr as DtlsSrtp; + Handler self = transport_ptr as Handler; DateTime current_time = new DateTime.now_utc(); current_time.add_seconds(ms/1000); @@ -184,6 +223,10 @@ public class DtlsSrtp { self.buffer_mutex.lock(); while (self.buffer_queue.size == 0) { self.buffer_cond.wait_until(self.buffer_mutex, end_time); + if (self.stop) { + self.buffer_mutex.unlock(); + return -1; + } DateTime new_current_time = new DateTime.now_utc(); if (new_current_time.compare(current_time) > 0) { @@ -197,7 +240,7 @@ public class DtlsSrtp { } private static ssize_t push_function(void* transport_ptr, uint8[] buffer) { - DtlsSrtp self = transport_ptr as DtlsSrtp; + Handler self = transport_ptr as Handler; self.send_data(buffer); // The callback should return a positive number indicating the bytes sent, and -1 on error. @@ -205,7 +248,7 @@ public class DtlsSrtp { } private static int verify_function(Session session) { - DtlsSrtp self = session.get_transport_pointer() as DtlsSrtp; + Handler self = session.get_transport_pointer() as Handler; try { bool valid = self.verify_peer_cert(session); if (!valid) { @@ -232,7 +275,17 @@ public class DtlsSrtp { X509.Certificate peer_cert = X509.Certificate.create(); peer_cert.import(ref cert_datums[0], CertificateFormat.DER); - uint8[] real_peer_fp = get_fingerprint(peer_cert, peer_fp_algo); + DigestAlgorithm algo; + switch (peer_fp_algo) { + case "sha-256": + algo = DigestAlgorithm.SHA256; + break; + default: + warning("Unkown peer fingerprint algorithm: %s", peer_fp_algo); + return false; + } + + uint8[] real_peer_fp = get_fingerprint(peer_cert, algo); if (real_peer_fp.length != this.peer_fingerprint.length) { warning("Fingerprint lengths not equal %i vs %i", real_peer_fp.length, peer_fingerprint.length); @@ -248,27 +301,34 @@ public class DtlsSrtp { return true; } +} - private uint8[] get_fingerprint(X509.Certificate certificate, DigestAlgorithm digest_algo) { - uint8[] buf = new uint8[512]; - size_t buf_out_size = 512; - certificate.get_fingerprint(digest_algo, buf, ref buf_out_size); +private uint8[] get_fingerprint(X509.Certificate certificate, DigestAlgorithm digest_algo) { + uint8[] buf = new uint8[512]; + size_t buf_out_size = 512; + certificate.get_fingerprint(digest_algo, buf, ref buf_out_size); - uint8[] ret = new uint8[buf_out_size]; - for (int i = 0; i < buf_out_size; i++) { - ret[i] = buf[i]; - } - return ret; + uint8[] ret = new uint8[buf_out_size]; + for (int i = 0; i < buf_out_size; i++) { + ret[i] = buf[i]; } - - 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 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 + return sb.str; +} + + +public enum Mode { + CLIENT, SERVER +} + +} diff --git a/plugins/ice/src/transport_parameters.vala b/plugins/ice/src/transport_parameters.vala index f95be261..e4862edc 100644 --- a/plugins/ice/src/transport_parameters.vala +++ b/plugins/ice/src/transport_parameters.vala @@ -9,11 +9,11 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport private bool we_want_connection; private bool remote_credentials_set; private Map connections = new HashMap(); - private DtlsSrtp? dtls_srtp; + private DtlsSrtp.Handler? dtls_srtp_handler; private class DatagramConnection : Jingle.DatagramConnection { private Nice.Agent agent; - private DtlsSrtp? dtls_srtp; + private DtlsSrtp.Handler? dtls_srtp_handler; private uint stream_id; private string? error; private ulong sent; @@ -22,9 +22,9 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport private ulong recv_reported; private ulong datagram_received_id; - public DatagramConnection(Nice.Agent agent, DtlsSrtp? dtls_srtp, uint stream_id, uint8 component_id) { + public DatagramConnection(Nice.Agent agent, DtlsSrtp.Handler? dtls_srtp_handler, uint stream_id, uint8 component_id) { this.agent = agent; - this.dtls_srtp = dtls_srtp; + this.dtls_srtp_handler = dtls_srtp_handler; this.stream_id = stream_id; this.component_id = component_id; this.datagram_received_id = this.datagram_received.connect((datagram) => { @@ -45,8 +45,8 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport public override void send_datagram(Bytes datagram) { if (this.agent != null && is_component_ready(agent, stream_id, component_id)) { uint8[] encrypted_data = null; - if (dtls_srtp != null) { - encrypted_data = dtls_srtp.process_outgoing_data(component_id, datagram.get_data()); + if (dtls_srtp_handler != null) { + encrypted_data = dtls_srtp_handler.process_outgoing_data(component_id, datagram.get_data()); if (encrypted_data == null) return; } agent.send(stream_id, component_id, encrypted_data ?? datagram.get_data()); @@ -65,13 +65,18 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport this.agent = agent; if (this.peer_fingerprint != null || !incoming) { - dtls_srtp = setup_dtls(this); - this.own_fingerprint = dtls_srtp.get_own_fingerprint(GnuTLS.DigestAlgorithm.SHA256); + dtls_srtp_handler = setup_dtls(this); + own_fingerprint = dtls_srtp_handler.own_fingerprint; if (incoming) { - dtls_srtp.set_peer_fingerprint(this.peer_fingerprint, this.peer_fp_algo == "sha-256" ? GnuTLS.DigestAlgorithm.SHA256 : GnuTLS.DigestAlgorithm.NULL); + own_setup = "active"; + dtls_srtp_handler.mode = DtlsSrtp.Mode.CLIENT; + dtls_srtp_handler.peer_fingerprint = peer_fingerprint; + dtls_srtp_handler.peer_fp_algo = peer_fp_algo; } else { - dtls_srtp.setup_dtls_connection.begin(true, (_, res) => { - this.content.encryption = dtls_srtp.setup_dtls_connection.end(res); + own_setup = "actpass"; + dtls_srtp_handler.mode = DtlsSrtp.Mode.SERVER; + dtls_srtp_handler.setup_dtls_connection.begin((_, res) => { + this.content.encryption = dtls_srtp_handler.setup_dtls_connection.end(res) ?? this.content.encryption; }); } } @@ -104,9 +109,9 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport agent.gather_candidates(stream_id); } - private static DtlsSrtp setup_dtls(TransportParameters tp) { + private static DtlsSrtp.Handler setup_dtls(TransportParameters tp) { var weak_self = new WeakRef(tp); - DtlsSrtp dtls_srtp = DtlsSrtp.setup(); + DtlsSrtp.Handler dtls_srtp = DtlsSrtp.setup(); dtls_srtp.send_data.connect((data) => { TransportParameters self = (TransportParameters) weak_self.get(); if (self != null) self.agent.send(self.stream_id, 1, data); @@ -144,10 +149,15 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport debug("on_transport_accept from %s", peer_full_jid.to_string()); base.handle_transport_accept(transport); - if (dtls_srtp != null && peer_fingerprint != null) { - dtls_srtp.set_peer_fingerprint(this.peer_fingerprint, this.peer_fp_algo == "sha-256" ? GnuTLS.DigestAlgorithm.SHA256 : GnuTLS.DigestAlgorithm.NULL); + if (dtls_srtp_handler != null && peer_fingerprint != null) { + dtls_srtp_handler.peer_fingerprint = peer_fingerprint; + dtls_srtp_handler.peer_fp_algo = peer_fp_algo; + if (peer_setup == "passive") { + dtls_srtp_handler.mode = DtlsSrtp.Mode.CLIENT; + dtls_srtp_handler.stop_dtls_connection(); + } } else { - dtls_srtp = null; + dtls_srtp_handler = null; } } @@ -200,18 +210,10 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport int new_candidates = agent.set_remote_candidates(stream_id, i, candidates); debug("Initiated component %u with %i remote candidates", i, new_candidates); - connections[i] = new DatagramConnection(agent, dtls_srtp, stream_id, i); + connections[i] = new DatagramConnection(agent, dtls_srtp_handler, stream_id, i); content.set_transport_connection(connections[i], i); } - if (incoming && dtls_srtp != null) { - Jingle.DatagramConnection rtp_datagram = (Jingle.DatagramConnection) content.get_transport_connection(1); - rtp_datagram.notify["ready"].connect(() => { - dtls_srtp.setup_dtls_connection.begin(false, (_, res) => { - this.content.encryption = dtls_srtp.setup_dtls_connection.end(res); - }); - }); - } base.create_transport_connection(stream, content); } @@ -219,11 +221,16 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport if (stream_id != this.stream_id) return; debug("stream %u component %u state changed to %s", stream_id, component_id, agent.get_component_state(stream_id, component_id).to_string()); may_consider_ready(stream_id, component_id); + if (incoming && dtls_srtp_handler != null && !dtls_srtp_handler.ready && is_component_ready(agent, stream_id, component_id) && dtls_srtp_handler.mode == DtlsSrtp.Mode.CLIENT) { + dtls_srtp_handler.setup_dtls_connection.begin((_, res) => { + this.content.encryption = dtls_srtp_handler.setup_dtls_connection.end(res) ?? this.content.encryption; + }); + } } private void may_consider_ready(uint stream_id, uint component_id) { if (stream_id != this.stream_id) return; - if (connections.has_key((uint8) component_id) && is_component_ready(agent, stream_id, component_id) && connections.has_key((uint8) component_id) && !connections[(uint8)component_id].ready) { + if (connections.has_key((uint8) component_id) && !connections[(uint8)component_id].ready && is_component_ready(agent, stream_id, component_id) && (dtls_srtp_handler == null || dtls_srtp_handler.ready)) { connections[(uint8)component_id].ready = true; } } @@ -241,8 +248,8 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport private void on_recv(Nice.Agent agent, uint stream_id, uint component_id, uint8[] data) { if (stream_id != this.stream_id) return; uint8[] decrypt_data = null; - if (dtls_srtp != null) { - decrypt_data = dtls_srtp.process_incoming_data(component_id, data); + if (dtls_srtp_handler != null) { + decrypt_data = dtls_srtp_handler.process_incoming_data(component_id, data); if (decrypt_data == null) return; } may_consider_ready(stream_id, component_id); @@ -317,4 +324,4 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport return candidate; } -} \ No newline at end of file +} diff --git a/xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala b/xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala index ac65f88c..c37c19cc 100644 --- a/xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala +++ b/xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala @@ -94,6 +94,9 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { ulong rtp_ready_handler_id = 0; rtp_ready_handler_id = rtp_datagram.notify["ready"].connect(() => { this.stream.on_rtp_ready(); + if (rtcp_mux) { + this.stream.on_rtcp_ready(); + } connection_ready(); rtp_datagram.disconnect(rtp_ready_handler_id); diff --git a/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala b/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala index f194f06d..4976f560 100644 --- a/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala +++ b/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala @@ -14,8 +14,10 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T public Gee.List remote_candidates = new ArrayList(Candidate.equals_func); public uint8[]? own_fingerprint = null; + public string? own_setup = null; public uint8[]? peer_fingerprint = null; public string? peer_fp_algo = null; + public string? peer_setup = null; public Jid local_full_jid { get; private set; } public Jid peer_full_jid { get; private set; } @@ -41,8 +43,9 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T StanzaNode? fingerprint_node = node.get_subnode("fingerprint", DTLS_NS_URI); if (fingerprint_node != null) { - peer_fingerprint = fingerprint_to_bytes(fingerprint_node.get_deep_string_content()); + peer_fingerprint = fingerprint_to_bytes(fingerprint_node.get_string_content()); peer_fp_algo = fingerprint_node.get_attribute("hash"); + peer_setup = fingerprint_node.get_attribute("setup"); } } } @@ -73,11 +76,7 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T .add_self_xmlns() .put_attribute("hash", "sha-256") .put_node(new StanzaNode.text(format_fingerprint(own_fingerprint))); - if (incoming) { - fingerprint_node.put_attribute("setup", "active"); - } else { - fingerprint_node.put_attribute("setup", "actpass"); - } + fingerprint_node.put_attribute("setup", own_setup); node.put_node(fingerprint_node); } @@ -101,6 +100,7 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T if (fingerprint_node != null) { peer_fingerprint = fingerprint_to_bytes(fingerprint_node.get_deep_string_content()); peer_fp_algo = fingerprint_node.get_attribute("hash"); + peer_setup = fingerprint_node.get_attribute("setup"); } } @@ -142,8 +142,6 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T } } - - private string format_fingerprint(uint8[] fingerprint) { var sb = new StringBuilder(); for (int i = 0; i < fingerprint.length; i++) { @@ -167,4 +165,4 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T } return bin; } -} \ No newline at end of file +} -- cgit v1.2.3-54-g00ecf