[calls] sip: Allow specifying if media encryption is desired



commit e75e04fb4e96900d9d3d1b0d2778c557a64ea7f8
Author: Evangelos Ribeiro Tzaras <devrtz fortysixandtwo eu>
Date:   Fri May 6 01:21:27 2022 +0200

    sip: Allow specifying if media encryption is desired
    
    A property of type SipMediaEncryption is added to both the origin and
    the call which allows to state if we want the media session to be
    encrypted with SRTP.
    
    Logic is added to interact with the CallsSdpCryptoContext if encryption
    is desired.

 plugins/sip/calls-sip-call.c   | 85 +++++++++++++++++++++++++++++++++++++++++-
 plugins/sip/calls-sip-call.h   |  4 ++
 plugins/sip/calls-sip-origin.c | 82 +++++++++++++++++++++++++++++++++++++++-
 plugins/sip/calls-sip-util.h   | 12 ++++++
 tests/test-media.c             |  2 +
 5 files changed, 182 insertions(+), 3 deletions(-)
---
diff --git a/plugins/sip/calls-sip-call.c b/plugins/sip/calls-sip-call.c
index 7842ef8d..8ea7b7ee 100644
--- a/plugins/sip/calls-sip-call.c
+++ b/plugins/sip/calls-sip-call.c
@@ -27,7 +27,9 @@
 
 #include "calls-call.h"
 #include "calls-message-source.h"
+#include "calls-sdp-crypto-context.h"
 #include "calls-sip-call.h"
+#include "calls-sip-enums.h"
 #include "calls-sip-media-manager.h"
 #include "calls-sip-media-pipeline.h"
 #include "calls-sip-util.h"
@@ -52,6 +54,7 @@ enum {
   PROP_CALL_HANDLE,
   PROP_IP,
   PROP_PIPELINE,
+  PROP_MEDIA_ENCRYPTION,
   PROP_LAST_PROP
 };
 static GParamSpec *props[PROP_LAST_PROP];
@@ -70,6 +73,9 @@ struct _CallsSipCall {
 
   nua_handle_t          *nh;
   GList                 *codecs;
+
+  CallsSdpCryptoContext *sdp_crypto_context;
+  SipMediaEncryption     media_encryption;
 };
 
 static void calls_sip_call_message_source_interface_init (CallsMessageSourceInterface *iface);
@@ -84,6 +90,8 @@ calls_sip_call_answer (CallsCall *call)
   CallsSipCall *self;
   g_autofree gchar *local_sdp = NULL;
   guint rtp_port, rtcp_port;
+  g_autoptr (GList) local_crypto = NULL;
+  gboolean got_crypto_offer;
 
   g_assert (CALLS_IS_CALL (call));
   g_assert (CALLS_IS_SIP_CALL (call));
@@ -100,11 +108,39 @@ calls_sip_call_answer (CallsCall *call)
   rtp_port = calls_sip_media_pipeline_get_rtp_port (self->pipeline);
   rtcp_port = calls_sip_media_pipeline_get_rtcp_port (self->pipeline);
 
+  got_crypto_offer =
+    calls_sdp_crypto_context_get_state (self->sdp_crypto_context) ==
+    CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE;
+
+  if (got_crypto_offer) {
+    if (self->media_encryption == SIP_MEDIA_ENCRYPTION_NONE) {
+      g_warning ("Encryption disabled, but got offer. Call should have already been declined!");
+      return;
+    }
+
+    if (!calls_sdp_crypto_context_generate_answer (self->sdp_crypto_context)) {
+      g_warning ("Could not generate answer for crypto key exchange. Aborting!");
+      CALLS_EMIT_MESSAGE(self, _("Cryptographic key exchange unsucessful"), GTK_MESSAGE_WARNING);
+      /* XXX this should (probably) never be reached */
+      nua_respond (self->nh, 488, "Not acceptable here", TAG_END ());
+      return;
+    }
+
+    local_crypto = calls_sdp_crypto_context_get_crypto_candidates (self->sdp_crypto_context, FALSE);
+  } else {
+    if (self->media_encryption == SIP_MEDIA_ENCRYPTION_FORCED) {
+      g_warning ("Encryption forced, but got no offer. Call should have already been declined!");
+      return;
+    } else if (self->media_encryption == SIP_MEDIA_ENCRYPTION_PREFERRED) {
+      g_debug ("Encryption optional, got no offer. Continuing unencrypted");
+    }
+  }
+
   local_sdp = calls_sip_media_manager_get_capabilities (self->manager,
                                                         self->ip,
                                                         rtp_port,
                                                         rtcp_port,
-                                                        NULL,
+                                                        local_crypto,
                                                         self->codecs);
 
   g_assert (local_sdp);
@@ -179,6 +215,10 @@ calls_sip_call_set_property (GObject      *object,
     self->pipeline = g_value_dup_object (value);
     break;
 
+  case PROP_MEDIA_ENCRYPTION:
+    self->media_encryption = g_value_get_enum (value);
+    break;
+
   default:
     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     break;
@@ -255,6 +295,14 @@ calls_sip_call_class_init (CallsSipCallClass *klass)
                          CALLS_TYPE_SIP_MEDIA_PIPELINE,
                          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
 
+  props[PROP_MEDIA_ENCRYPTION] =
+    g_param_spec_enum ("media-encryption",
+                       "Media encryption",
+                       "The media encryption mode",
+                       SIP_TYPE_MEDIA_ENCRYPTION,
+                       SIP_MEDIA_ENCRYPTION_NONE,
+                       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
+
   g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
 }
 
@@ -269,6 +317,7 @@ static void
 calls_sip_call_init (CallsSipCall *self)
 {
   self->manager = calls_sip_media_manager_default ();
+  self->sdp_crypto_context = calls_sdp_crypto_context_new ();
 }
 
 
@@ -321,6 +370,24 @@ calls_sip_call_activate_media (CallsSipCall *self,
   g_return_if_fail (CALLS_IS_SIP_MEDIA_PIPELINE (self->pipeline));
 
   if (enabled) {
+    gboolean negotiated = calls_sdp_crypto_context_get_is_negotiated (self->sdp_crypto_context);
+
+    if (negotiated) {
+      calls_srtp_crypto_attribute *remote_crypto =
+        calls_sdp_crypto_context_get_remote_crypto (self->sdp_crypto_context);
+      calls_srtp_crypto_attribute *local_crypto =
+        calls_sdp_crypto_context_get_local_crypto (self->sdp_crypto_context);
+
+      calls_sip_media_pipeline_set_crypto (self->pipeline, local_crypto, remote_crypto);
+    } else {
+      if (self->media_encryption == SIP_MEDIA_ENCRYPTION_FORCED) {
+        g_warning ("Encryption is forced, but parameters were not negotiated! Aborting");
+        return;
+      } else if (self->media_encryption == SIP_MEDIA_ENCRYPTION_PREFERRED) {
+        g_debug ("No encryption parameters negotiated, continuing unencrypted");
+      }
+    }
+
     if (calls_sip_media_pipeline_get_state (self->pipeline) ==
         CALLS_MEDIA_PIPELINE_STATE_NEED_CODEC) {
       MediaCodecInfo *codec = (MediaCodecInfo *) self->codecs->data;
@@ -341,6 +408,7 @@ calls_sip_call_new (const gchar           *id,
                     gboolean               inbound,
                     const char            *own_ip,
                     CallsSipMediaPipeline *pipeline,
+                    SipMediaEncryption     media_encryption,
                     nua_handle_t          *handle)
 {
   g_return_val_if_fail (id, NULL);
@@ -350,6 +418,7 @@ calls_sip_call_new (const gchar           *id,
                        "inbound", inbound,
                        "own-ip", own_ip,
                        "pipeline", pipeline,
+                       "media-encryption", media_encryption,
                        "nua-handle", handle,
                        "call-type", CALLS_CALL_TYPE_SIP_VOICE,
                        NULL);
@@ -372,3 +441,17 @@ calls_sip_call_set_codecs (CallsSipCall *self,
   g_list_free (self->codecs);
   self->codecs = g_list_copy (codecs);
 }
+
+/**
+ * calls_sip_call_get_sdp_crypto_context:
+ * @self: A #CallsSipCall
+ *
+ * Returns: (transfer full): The #CallsSdpCryptoContext of this call
+ */
+CallsSdpCryptoContext *
+calls_sip_call_get_sdp_crypto_context (CallsSipCall *self)
+{
+  g_return_val_if_fail (CALLS_IS_CALL (self), NULL);
+
+  return g_object_ref (self->sdp_crypto_context);
+}
diff --git a/plugins/sip/calls-sip-call.h b/plugins/sip/calls-sip-call.h
index 5c9c1891..60c1cdf9 100644
--- a/plugins/sip/calls-sip-call.h
+++ b/plugins/sip/calls-sip-call.h
@@ -25,7 +25,9 @@
 #pragma once
 
 #include "calls-call.h"
+#include "calls-sdp-crypto-context.h"
 #include "calls-sip-media-pipeline.h"
+#include "calls-sip-util.h"
 
 #include <glib-object.h>
 #include <sofia-sip/nua.h>
@@ -40,6 +42,7 @@ CallsSipCall          *calls_sip_call_new                               (const c
                                                                          gboolean               inbound,
                                                                          const char            *own_ip,
                                                                          CallsSipMediaPipeline *pipeline,
+                                                                         SipMediaEncryption     encryption,
                                                                          nua_handle_t          *handle);
 void                   calls_sip_call_setup_remote_media_connection     (CallsSipCall *self,
                                                                          const char   *remote,
@@ -51,5 +54,6 @@ void                   calls_sip_call_set_state                         (CallsSi
                                                                          CallsCallState state);
 void                   calls_sip_call_set_codecs                        (CallsSipCall *self,
                                                                          GList        *codecs);
+CallsSdpCryptoContext *calls_sip_call_get_sdp_crypto_context            (CallsSipCall *self);
 
 G_END_DECLS
diff --git a/plugins/sip/calls-sip-origin.c b/plugins/sip/calls-sip-origin.c
index 582ab89b..57451de1 100644
--- a/plugins/sip/calls-sip-origin.c
+++ b/plugins/sip/calls-sip-origin.c
@@ -76,6 +76,7 @@ enum {
   PROP_CALLS,
   PROP_COUNTRY_CODE,
   PROP_CAN_TEL,
+  PROP_MEDIA_ENCRYPTION,
   PROP_LAST_PROP,
 };
 static GParamSpec *props[PROP_LAST_PROP];
@@ -111,6 +112,7 @@ struct _CallsSipOrigin {
   gboolean              auto_connect;
   gboolean              direct_mode;
   gboolean              can_tel;
+  SipMediaEncryption    media_encryption;
 
   char                 *own_ip;
   gint                  local_port;
@@ -257,7 +259,12 @@ add_call (CallsSipOrigin *self,
       call_address = address_split[1];
   }
 
-  sip_call = calls_sip_call_new (call_address, inbound, self->own_ip, pipeline, handle);
+  sip_call = calls_sip_call_new (call_address,
+                                 inbound,
+                                 self->own_ip,
+                                 pipeline,
+                                 self->media_encryption,
+                                 handle);
   g_assert (sip_call != NULL);
 
   if (self->oper->call_handle)
@@ -276,11 +283,30 @@ add_call (CallsSipOrigin *self,
                     self);
 
   if (!inbound) {
+    g_autoptr (GList) crypto_attributes = NULL;
+    g_autoptr (CallsSdpCryptoContext) ctx =
+      calls_sip_call_get_sdp_crypto_context (sip_call);
+
+    if (self->media_encryption == SIP_MEDIA_ENCRYPTION_FORCED) {
+      if (!calls_sdp_crypto_context_generate_offer (ctx)) {
+        g_warning ("Media encryption must be used, but could not generate offer. Aborting");
+        calls_call_set_state (CALLS_CALL (sip_call), CALLS_CALL_STATE_DISCONNECTED);
+        return;
+      }
+    }
+
+    if (self->media_encryption == SIP_MEDIA_ENCRYPTION_PREFERRED) {
+      if (!calls_sdp_crypto_context_generate_offer (ctx))
+        g_debug ("Media encryption optional, but could not generate offer. Continuing unencrypted");
+    }
+
+    crypto_attributes = calls_sdp_crypto_context_get_crypto_candidates (ctx, FALSE);
+
     local_sdp = calls_sip_media_manager_static_capabilities (self->media_manager,
                                                              self->own_ip,
                                                              rtp_port,
                                                              rtcp_port,
-                                                             NULL);
+                                                             crypto_attributes);
 
     g_assert (local_sdp);
 
@@ -567,6 +593,7 @@ sip_i_state (int              status,
     g_autoptr (GList) codecs =
       calls_sip_media_manager_get_codecs_from_sdp (origin->media_manager,
                                                    r_sdp->sdp_media);
+    g_autoptr (CallsSdpCryptoContext) ctx = NULL;
     const char *session_ip = NULL;
     const char *media_ip = NULL;
     int rtp_port;
@@ -580,6 +607,40 @@ sip_i_state (int              status,
       return;
     }
 
+    ctx = calls_sip_call_get_sdp_crypto_context (call);
+    if (origin->media_encryption == SIP_MEDIA_ENCRYPTION_FORCED) {
+
+      if (!calls_sdp_crypto_context_set_remote_media (ctx, r_sdp->sdp_media)) {
+        g_warning ("Media encryption is enforced, but remote didn't set crypto attributes.\n"
+                   "Remote SDP: %s", r_sdp_str);
+        calls_call_hang_up (CALLS_CALL (call));
+        return;
+      }
+    }
+
+    if (origin->media_encryption == SIP_MEDIA_ENCRYPTION_PREFERRED) {
+      if (!calls_sdp_crypto_context_set_remote_media (ctx, r_sdp->sdp_media))
+        g_debug ("Remote didn't set crypto attributes. Continuing unencrypted");
+    }
+
+    if (origin->media_encryption == SIP_MEDIA_ENCRYPTION_NONE) {
+      gboolean got_crypto = FALSE;
+
+      for (sdp_attribute_t *attr = r_sdp->sdp_media->m_attributes; attr; attr = attr->a_next) {
+        if (g_strcmp0 (attr->a_name, "crypto") == 0) {
+          got_crypto = TRUE;
+          break;
+        }
+      }
+
+      if (got_crypto) {
+        g_debug ("Remote peer %s wants to talk using encryption, but not allowed by local policy",
+                 calls_call_get_id (CALLS_CALL (call)));
+        calls_call_hang_up (CALLS_CALL (call));
+        return;
+      }
+    }
+
     if (r_sdp->sdp_connection && r_sdp->sdp_connection->c_address)
       session_ip = r_sdp->sdp_connection->c_address;
 
@@ -1290,6 +1351,10 @@ calls_sip_origin_set_property (GObject      *object,
     self->can_tel = g_value_get_boolean (value);
     break;
 
+  case PROP_MEDIA_ENCRYPTION:
+    self->media_encryption = g_value_get_enum (value);
+    break;
+
   default:
     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     break;
@@ -1370,6 +1435,10 @@ calls_sip_origin_get_property (GObject    *object,
     g_value_set_boolean (value, self->can_tel);
     break;
 
+  case PROP_MEDIA_ENCRYPTION:
+    g_value_set_enum (value, self->media_encryption);
+    break;
+
   default:
     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     break;
@@ -1538,6 +1607,15 @@ calls_sip_origin_class_init (CallsSipOriginClass *klass)
                           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
   g_object_class_install_property (object_class, PROP_CAN_TEL, props[PROP_CAN_TEL]);
 
+  props[PROP_MEDIA_ENCRYPTION] =
+    g_param_spec_enum ("media-encryption",
+                       "Media encryption",
+                       "The media encryption mode",
+                       SIP_TYPE_MEDIA_ENCRYPTION,
+                       SIP_MEDIA_ENCRYPTION_NONE,
+                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_MEDIA_ENCRYPTION, props[PROP_MEDIA_ENCRYPTION]);
+
 #define IMPLEMENTS(ID, NAME) \
   g_object_class_override_property (object_class, ID, NAME);    \
   props[ID] = g_object_class_find_property(object_class, NAME);
diff --git a/plugins/sip/calls-sip-util.h b/plugins/sip/calls-sip-util.h
index a87af6e4..4436e19c 100644
--- a/plugins/sip/calls-sip-util.h
+++ b/plugins/sip/calls-sip-util.h
@@ -54,6 +54,18 @@ typedef enum {
   SIP_ENGINE_READY,
 } SipEngineState;
 
+/**
+ * SipMediaEncryption:
+ * @SIP_MEDIA_ENCRYPTION_NONE: Don't encrypt media streams
+ * @SIP_MEDIA_ENCRYPTION_PREFERRED: Prefer using encryption, but also allow unencrypted media
+ * @SIP_MEDIA_ENCRYPTION_FORCED: Force using encryption, drop unencrypted calls
+ */
+typedef enum {
+  SIP_MEDIA_ENCRYPTION_NONE = 0,
+  SIP_MEDIA_ENCRYPTION_PREFERRED,
+  SIP_MEDIA_ENCRYPTION_FORCED,
+} SipMediaEncryption;
+
 
 gboolean    check_sips                         (const char *addr);
 gboolean    check_ipv6                         (const char *host);
diff --git a/tests/test-media.c b/tests/test-media.c
index 47f8c09b..874d7df6 100644
--- a/tests/test-media.c
+++ b/tests/test-media.c
@@ -249,6 +249,7 @@ test_media_pipeline_finalized_in_call (void)
                                            TRUE,
                                            "127.0.0.1",
                                            pipeline,
+                                           SIP_MEDIA_ENCRYPTION_NONE,
                                            NULL);
 
   g_object_unref (call);
@@ -259,6 +260,7 @@ test_media_pipeline_finalized_in_call (void)
                              TRUE,
                              "127.0.0.1",
                              pipeline,
+                             SIP_MEDIA_ENCRYPTION_NONE,
                              NULL);
   g_object_unref (call);
   g_assert_finalize_object (pipeline);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]