[calls] sip: Add SDP crypto context class



commit 14350a38ed8466de1bd554d47a0f084fd48bcd82
Author: Evangelos Ribeiro Tzaras <devrtz fortysixandtwo eu>
Date:   Tue Apr 26 13:07:58 2022 +0200

    sip: Add SDP crypto context class
    
    Objects of this type keep track of SDP of the local and remote peers,
    allow generating offers and answers and codify default policy used for
    cryptographic parameters.

 plugins/sip/calls-sdp-crypto-context.c | 512 +++++++++++++++++++++++++++++++++
 plugins/sip/calls-sdp-crypto-context.h |  66 +++++
 plugins/sip/meson.build                |   2 +
 tests/meson.build                      |  15 +
 tests/test-sdp-crypto.c                | 177 ++++++++++++
 5 files changed, 772 insertions(+)
---
diff --git a/plugins/sip/calls-sdp-crypto-context.c b/plugins/sip/calls-sdp-crypto-context.c
new file mode 100644
index 00000000..fda89e88
--- /dev/null
+++ b/plugins/sip/calls-sdp-crypto-context.c
@@ -0,0 +1,512 @@
+/*
+ * Copyright (C) 2022 Purism SPC
+ *
+ * This file is part of Calls.
+ *
+ * Calls is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Calls is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Calls.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Evangelos Ribeiro Tzaras <evangelos tzaras puri sm>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#include "calls-sdp-crypto-context.h"
+#include "calls-sip-enums.h"
+
+#include <sofia-sip/sdp.h>
+
+enum {
+  PROP_0,
+  PROP_STATE,
+  PROP_LAST_PROP
+};
+static GParamSpec *props[PROP_LAST_PROP];
+
+struct _CallsSdpCryptoContext {
+  GObject                 parent_instance;
+
+  GList                  *local_crypto_attributes;
+  GList                  *remote_crypto_attributes;
+
+  CallsCryptoContextState state;
+  int                     negotiated_tag;
+};
+
+#if GLIB_CHECK_VERSION (2, 70, 0)
+G_DEFINE_FINAL_TYPE (CallsSdpCryptoContext, calls_sdp_crypto_context, G_TYPE_OBJECT)
+#else
+G_DEFINE_TYPE (CallsSdpCryptoContext, calls_sdp_crypto_context, G_TYPE_OBJECT)
+#endif
+
+
+static GStrv
+get_all_crypto_attributes_strv (sdp_media_t *media)
+{
+#if GLIB_CHECK_VERSION (2, 68, 0)
+  g_autoptr (GStrvBuilder) builder = NULL;
+
+  g_assert (media);
+
+  builder = g_strv_builder_new ();
+
+  for (sdp_attribute_t *attr = media->m_attributes; attr; attr = attr->a_next) {
+    g_autofree char *crypto_str = NULL;
+
+    if (g_strcmp0 (attr->a_name, "crypto") != 0)
+      continue;
+
+    crypto_str = g_strconcat ("a=crypto:", attr->a_value, NULL);
+    g_strv_builder_add (builder, crypto_str);
+  }
+
+  return g_strv_builder_end (builder);
+#else
+  /* implement a poor mans GStrv */
+  g_autofree char *attribute_string = NULL;
+
+  g_assert (media);
+
+  for (sdp_attribute_t *attr = media->m_attributes; attr; attr = attr->a_next) {
+    g_autofree char *crypto_str = NULL;
+
+    if (g_strcmp0 (attr->a_name, "crypto") != 0)
+      continue;
+
+    crypto_str = g_strconcat ("a=crypto:", attr->a_value, NULL);
+    if (!attribute_string) {
+      attribute_string = g_strdup (crypto_str);
+    } else {
+      g_autofree char *tmp = attribute_string;
+      attribute_string = g_strconcat (attribute_string, "\n", crypto_str, NULL);
+    }
+  }
+  return g_strsplit (attribute_string, "\n", -1);
+#endif
+}
+
+
+static gboolean
+crypto_attribute_is_supported (CallsSdpCryptoContext       *self,
+                               calls_srtp_crypto_attribute *attr)
+{
+  g_assert (attr);
+
+  if (attr->crypto_suite == CALLS_SRTP_SUITE_UNKNOWN)
+    return FALSE;
+
+  /* TODO setup a policy mechanism, for now this is hardcoded */
+  if (attr->unencrypted_srtp ||
+      attr->unauthenticated_srtp ||
+      attr->unencrypted_srtcp)
+    return FALSE;
+
+  return TRUE;
+}
+
+static calls_srtp_crypto_attribute *
+get_crypto_attribute_by_tag (GList *attributes,
+                             guint  tag)
+{
+
+  g_assert (attributes);
+  g_assert (tag > 0);
+
+  for (GList *node = attributes; node; node = node->next) {
+    calls_srtp_crypto_attribute *attr = node->data;
+
+    if (attr->tag == tag)
+      return attr;
+  }
+
+  return NULL;
+}
+
+
+static void
+set_state (CallsSdpCryptoContext  *self,
+           CallsCryptoContextState state)
+{
+  g_assert (CALLS_IS_SDP_CRYPTO_CONTEXT (self));
+
+  if (self->state == state)
+    return;
+
+  self->state = state;
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STATE]);
+}
+
+
+/* Returns if %TRUE if the state was updated, %FALSE otherwise */
+static gboolean
+update_state (CallsSdpCryptoContext *self)
+{
+  GList *tags_local = NULL;
+  GList *tags_remote = NULL;
+  gint negotiated_tag = -1;
+  calls_srtp_crypto_attribute *local_crypto;
+  calls_srtp_crypto_attribute *remote_crypto;
+
+
+  g_assert (CALLS_IS_SDP_CRYPTO_CONTEXT (self));
+
+  /* Cannot update final states */
+  if (self->state == CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_FAILED ||
+      self->state == CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS)
+    return FALSE;
+
+  if (self->state == CALLS_CRYPTO_CONTEXT_STATE_INIT) {
+    if (self->local_crypto_attributes) {
+      set_state (self, CALLS_CRYPTO_CONTEXT_STATE_OFFER_LOCAL);
+      return TRUE;
+    }
+    if (self->remote_crypto_attributes) {
+      set_state (self, CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE);
+      return TRUE;
+    }
+
+    return FALSE;
+  }
+
+  for (GList *node = self->local_crypto_attributes; node; node = node->next) {
+    calls_srtp_crypto_attribute *attr = node->data;
+    tags_local = g_list_append (tags_local, GUINT_TO_POINTER (attr->tag));
+  }
+
+  for (GList *node = self->remote_crypto_attributes; node; node = node->next) {
+    calls_srtp_crypto_attribute *attr = node->data;
+    tags_remote = g_list_append (tags_remote, GUINT_TO_POINTER (attr->tag));
+  }
+
+  if (self->state == CALLS_CRYPTO_CONTEXT_STATE_OFFER_LOCAL) {
+    for (GList *node = tags_local; node; node = node->next) {
+      if (g_list_find (tags_remote, node->data)) {
+        negotiated_tag = GPOINTER_TO_UINT (node->data);
+        break;
+      }
+    }
+  } else if (self->state == CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE) {
+    for (GList *node = tags_remote; node; node = node->next) {
+      if (g_list_find (tags_local, node->data)) {
+        negotiated_tag = GPOINTER_TO_UINT (node->data);
+        break;
+      }
+    }
+  } else {
+    g_assert_not_reached ();
+  }
+
+  g_list_free (tags_local);
+  g_list_free (tags_remote);
+
+  if (negotiated_tag == -1) {
+    self->state = CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_FAILED;
+    return TRUE;
+  }
+
+  local_crypto = get_crypto_attribute_by_tag (self->local_crypto_attributes,
+                                              negotiated_tag);
+  remote_crypto = get_crypto_attribute_by_tag (self->remote_crypto_attributes,
+                                               negotiated_tag);
+
+  if (local_crypto->crypto_suite != remote_crypto->crypto_suite) {
+    set_state (self, CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_FAILED);
+    return TRUE;
+  }
+
+  /* TODO check mandatory parameters and policy constrains */
+
+  self->negotiated_tag = negotiated_tag;
+  set_state (self, CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS);
+
+  return TRUE;
+}
+
+
+static void
+calls_sdp_crypto_context_get_property (GObject    *object,
+                                       guint       property_id,
+                                       GValue     *value,
+                                       GParamSpec *pspec)
+{
+  CallsSdpCryptoContext *self = CALLS_SDP_CRYPTO_CONTEXT (object);
+
+  switch (property_id) {
+  case PROP_STATE:
+    g_value_set_enum (value, self->state);
+    break;
+
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    break;
+  }
+}
+
+
+static void
+calls_sdp_crypto_context_dispose (GObject *object)
+{
+  CallsSdpCryptoContext *self = CALLS_SDP_CRYPTO_CONTEXT (object);
+
+  g_clear_list (&self->local_crypto_attributes, (GDestroyNotify) calls_srtp_crypto_attribute_free);
+  g_clear_list (&self->remote_crypto_attributes, (GDestroyNotify) calls_srtp_crypto_attribute_free);
+
+  G_OBJECT_CLASS (calls_sdp_crypto_context_parent_class)->dispose (object);
+}
+
+
+static void
+calls_sdp_crypto_context_class_init (CallsSdpCryptoContextClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = calls_sdp_crypto_context_dispose;
+  object_class->get_property = calls_sdp_crypto_context_get_property;
+
+  props[PROP_STATE] =
+    g_param_spec_enum ("state",
+                       "State",
+                       "State of the crypto context",
+                       CALLS_TYPE_CRYPTO_CONTEXT_STATE,
+                       CALLS_CRYPTO_CONTEXT_STATE_INIT,
+                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
+}
+
+
+static void
+calls_sdp_crypto_context_init (CallsSdpCryptoContext *self)
+{
+}
+
+
+CallsSdpCryptoContext *
+calls_sdp_crypto_context_new (void)
+{
+  return g_object_new (CALLS_TYPE_SDP_CRYPTO_CONTEXT, NULL);
+}
+
+
+gboolean
+calls_sdp_crypto_context_set_local_media (CallsSdpCryptoContext *self,
+                                          sdp_media_t           *media)
+{
+  g_auto (GStrv) crypto_strv = NULL;
+  guint n_crypto_attr;
+
+  g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE);
+  g_return_val_if_fail (media, FALSE);
+
+  if (self->local_crypto_attributes) {
+    g_warning ("Local crypto attributes already set");
+    return FALSE;
+  }
+
+  crypto_strv = get_all_crypto_attributes_strv (media);
+  n_crypto_attr = g_strv_length (crypto_strv);
+
+  if (n_crypto_attr == 0) {
+    g_warning ("No crypto attributes found in given SDP media");
+    return FALSE;
+  }
+
+  for (guint i = 0; i < n_crypto_attr; i++) {
+    g_autoptr (GError) error = NULL;
+    calls_srtp_crypto_attribute *attr;
+
+    attr = calls_srtp_parse_sdp_crypto_attribute (crypto_strv[i], &error);
+    if (!attr) {
+      g_warning ("Failed parsing crypto attribute '%s': %s",
+                 crypto_strv[i], error->message);
+      continue;
+    }
+    self->local_crypto_attributes =
+      g_list_append (self->local_crypto_attributes, attr);
+  }
+
+  if (!self->local_crypto_attributes) {
+    g_warning ("Could not parse a single crypto attribute, aborting");
+    return FALSE;
+  }
+
+  return update_state (self);
+}
+
+
+gboolean
+calls_sdp_crypto_context_set_remote_media (CallsSdpCryptoContext *self,
+                                           sdp_media_t           *media)
+{
+  g_auto (GStrv) crypto_strv = NULL;
+
+  guint n_crypto_attr;
+
+  g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE);
+  g_return_val_if_fail (media, FALSE);
+
+  if (self->remote_crypto_attributes) {
+    g_warning ("Remote crypto attributes already set");
+    return FALSE;
+  }
+
+  crypto_strv = get_all_crypto_attributes_strv (media);
+  n_crypto_attr = g_strv_length (crypto_strv);
+
+  if (n_crypto_attr == 0) {
+    g_warning ("No crypto attributes found in given SDP media");
+    return FALSE;
+  }
+
+  for (guint i = 0; i < n_crypto_attr; i++) {
+    g_autoptr (GError) error = NULL;
+    calls_srtp_crypto_attribute *attr;
+
+    attr = calls_srtp_parse_sdp_crypto_attribute (crypto_strv[i], &error);
+    if (!attr) {
+      g_warning ("Failed parsing crypto attribute '%s': %s",
+                 crypto_strv[i], error->message);
+      continue;
+    }
+    self->remote_crypto_attributes =
+      g_list_append (self->remote_crypto_attributes, attr);
+  }
+
+  if (!self->remote_crypto_attributes) {
+    g_warning ("Could not parse a single crypto attribute, aborting");
+    return FALSE;
+  }
+
+  return update_state (self);
+}
+
+
+calls_srtp_crypto_attribute *
+calls_sdp_crypto_context_get_local_crypto (CallsSdpCryptoContext *self)
+{
+  g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), NULL);
+
+  if (self->state != CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS)
+    return NULL;
+
+  return get_crypto_attribute_by_tag (self->local_crypto_attributes,
+                                      self->negotiated_tag);
+}
+
+
+calls_srtp_crypto_attribute *
+calls_sdp_crypto_context_get_remote_crypto (CallsSdpCryptoContext *self)
+{
+  g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), NULL);
+
+  if (self->state != CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS)
+    return NULL;
+
+  return get_crypto_attribute_by_tag (self->remote_crypto_attributes,
+                                      self->negotiated_tag);
+}
+
+
+gboolean
+calls_sdp_crypto_context_generate_offer (CallsSdpCryptoContext *self)
+{
+  calls_srtp_crypto_attribute *attr;
+
+  g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE);
+
+  if (self->state != CALLS_CRYPTO_CONTEXT_STATE_INIT) {
+    g_warning ("Cannot generate offer. Need INIT state, but found %d",
+               self->state);
+    return FALSE;
+  }
+
+  g_assert (!self->local_crypto_attributes);
+
+  attr = calls_srtp_crypto_attribute_new (1);
+  attr->tag = 1;
+  attr->crypto_suite = CALLS_SRTP_SUITE_AES_128_SHA1_80;
+  calls_srtp_crypto_attribute_init_keys (attr);
+
+  self->local_crypto_attributes = g_list_append (NULL, attr);
+
+  attr = calls_srtp_crypto_attribute_new (1);
+  attr->tag = 2;
+  attr->crypto_suite = CALLS_SRTP_SUITE_AES_128_SHA1_32;
+  calls_srtp_crypto_attribute_init_keys (attr);
+
+  self->local_crypto_attributes = g_list_append (self->local_crypto_attributes, attr);
+
+  return update_state (self);
+}
+
+
+gboolean
+calls_sdp_crypto_context_generate_answer (CallsSdpCryptoContext *self)
+{
+  calls_srtp_crypto_attribute *attr = NULL;
+
+  g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE);
+
+  if (self->state != CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE) {
+    g_warning ("Cannot generate answer. Need OFFER_REMOTE state, but found %d",
+               self->state);
+    return FALSE;
+  }
+
+  for (GList *node = self->remote_crypto_attributes; node; node = node->next) {
+    calls_srtp_crypto_attribute *attr_offer = node->data;
+
+    if (crypto_attribute_is_supported (self, attr_offer)) {
+      attr = calls_srtp_crypto_attribute_new (1);
+      attr->crypto_suite = attr_offer->crypto_suite;
+      attr->tag = attr_offer->tag;
+      calls_srtp_crypto_attribute_init_keys (attr);
+    }
+  }
+
+  if (!attr)
+    return FALSE;
+
+  self->local_crypto_attributes = g_list_append (NULL, attr);
+
+  return update_state (self);
+}
+
+
+gboolean
+calls_sdp_crypto_context_get_is_negotiated (CallsSdpCryptoContext *self)
+{
+  g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE);
+
+  return self->state == CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS;
+}
+
+
+CallsCryptoContextState
+calls_sdp_crypto_context_get_state (CallsSdpCryptoContext *self)
+{
+  g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), CALLS_CRYPTO_CONTEXT_STATE_INIT);
+
+  return self->state;
+}
+
+
+GList *
+calls_sdp_crypto_context_get_crypto_candidates (CallsSdpCryptoContext *self,
+                                                gboolean               remote)
+{
+  g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), NULL);
+
+  return g_list_copy (remote ? self->remote_crypto_attributes : self->local_crypto_attributes);
+}
diff --git a/plugins/sip/calls-sdp-crypto-context.h b/plugins/sip/calls-sdp-crypto-context.h
new file mode 100644
index 00000000..efb6fed5
--- /dev/null
+++ b/plugins/sip/calls-sdp-crypto-context.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 Purism SPC
+ *
+ * This file is part of Calls.
+ *
+ * Calls is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Calls is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Calls.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Evangelos Ribeiro Tzaras <evangelos tzaras puri sm>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#pragma once
+
+#include "calls-srtp-utils.h"
+
+#include <glib-object.h>
+
+#include <sofia-sip/sdp.h>
+
+G_BEGIN_DECLS
+
+/** XXX media line with cryptographic key parameters or session parameters that we
+ * do not support MUST be rejected (https://datatracker.ietf.org/doc/html/rfc4568#section-7.1.2)
+ */
+
+typedef enum {
+  CALLS_CRYPTO_CONTEXT_STATE_INIT = 0,
+  CALLS_CRYPTO_CONTEXT_STATE_OFFER_LOCAL,
+  CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE,
+  CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_FAILED,
+  CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS
+} CallsCryptoContextState;
+
+
+#define CALLS_TYPE_SDP_CRYPTO_CONTEXT (calls_sdp_crypto_context_get_type ())
+
+G_DECLARE_FINAL_TYPE (CallsSdpCryptoContext, calls_sdp_crypto_context, CALLS, SDP_CRYPTO_CONTEXT, GObject);
+
+CallsSdpCryptoContext       *calls_sdp_crypto_context_new                   (void);
+gboolean                     calls_sdp_crypto_context_set_local_media       (CallsSdpCryptoContext *self,
+                                                                             sdp_media_t           *media);
+gboolean                     calls_sdp_crypto_context_set_remote_media      (CallsSdpCryptoContext *self,
+                                                                             sdp_media_t           *media);
+calls_srtp_crypto_attribute *calls_sdp_crypto_context_get_local_crypto      (CallsSdpCryptoContext *self);
+calls_srtp_crypto_attribute *calls_sdp_crypto_context_get_remote_crypto     (CallsSdpCryptoContext *self);
+gboolean                     calls_sdp_crypto_context_generate_offer        (CallsSdpCryptoContext *self);
+gboolean                     calls_sdp_crypto_context_generate_answer       (CallsSdpCryptoContext *self);
+gboolean                     calls_sdp_crypto_context_get_is_negotiated     (CallsSdpCryptoContext *self);
+CallsCryptoContextState      calls_sdp_crypto_context_get_state             (CallsSdpCryptoContext *self);
+GList                       *calls_sdp_crypto_context_get_crypto_candidates (CallsSdpCryptoContext *self,
+                                                                             gboolean               remote);
+
+G_END_DECLS
diff --git a/plugins/sip/meson.build b/plugins/sip/meson.build
index 9b3e1dc2..65959c9a 100644
--- a/plugins/sip/meson.build
+++ b/plugins/sip/meson.build
@@ -44,6 +44,7 @@ sip_deps = [
 
 sip_sources = files(
   [
+    'calls-sdp-crypto-context.c', 'calls-sdp-crypto-context.h',
     'calls-sip-call.c', 'calls-sip-call.h',
     'calls-sip-origin.c', 'calls-sip-origin.h',
     'calls-sip-provider.c', 'calls-sip-provider.h',
@@ -65,6 +66,7 @@ pipeline_enums = gnome.mkenums_simple('calls-media-pipeline-enums',
 sip_sources += pipeline_enums
 
 sip_enum_headers = [
+  'calls-sdp-crypto-context.h',
   'calls-sip-util.h',
 ]
 
diff --git a/tests/meson.build b/tests/meson.build
index 1f68d2c1..fa45543d 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -138,6 +138,21 @@ t = executable('srtp', test_sources,
                )
 test('srtp', t, env: test_env)
 
+test_sources = [ 'test-sdp-crypto.c' ]
+test_sources += sip_sources
+t = executable('sdp-crypto', test_sources,
+               c_args : test_cflags,
+               link_args: test_link_args,
+               pie: true,
+               link_with : [calls_vala, libcalls],
+               dependencies: [calls_deps, sip_deps],
+               include_directories : [
+                   calls_includes,
+                   sip_include,
+                 ]
+               )
+test('sdp-crypto', t, env: test_env)
+
 test_sources = [ 'test-util.c' ]
 t = executable('util', test_sources,
                c_args : test_cflags,
diff --git a/tests/test-sdp-crypto.c b/tests/test-sdp-crypto.c
new file mode 100644
index 00000000..df733bfc
--- /dev/null
+++ b/tests/test-sdp-crypto.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2022 Purism SPC
+ *
+ * SPDX-License-Identifier: GPL-3.0+
+ *
+ * Author: Evangelos Ribeiro Tzaras <evangelos tzaras puri sm>
+ */
+
+#include "calls-sdp-crypto-context.h"
+
+#include <gtk/gtk.h>
+
+
+static void
+test_crypto_manual (void)
+{
+  g_autoptr (CallsSdpCryptoContext) ctx = NULL;
+  g_autofree guchar *offer_key = NULL;
+  g_autofree char *offer_key_b64 = NULL;
+  g_autofree char *offer_crypto_attr = NULL;
+  g_autofree char *offer_full = NULL;
+  g_autofree guchar *answer_key = NULL;
+  g_autofree char *answer_key_b64 = NULL;
+  g_autofree char *answer_crypto_attr = NULL;
+  g_autofree char *answer_full = NULL;
+  calls_srtp_crypto_attribute *offer_attr;
+  calls_srtp_crypto_attribute *answer_attr;
+
+  su_home_t home;
+  sdp_parser_t *sdp_parser_offer;
+  sdp_session_t *sdp_offer;
+  sdp_parser_t *sdp_parser_answer;
+  sdp_session_t *sdp_answer;
+
+  su_home_init (&home);
+
+  ctx = calls_sdp_crypto_context_new ();
+
+  offer_key = calls_srtp_generate_key_salt (30);
+  offer_key_b64 = g_base64_encode (offer_key, 30);
+  g_debug ("Offer key: %s", offer_key_b64);
+
+  offer_crypto_attr = g_strdup_printf ("a=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:%s",
+                                       offer_key_b64);
+  offer_full = g_strdup_printf ("m=audio 21000 RTP/SAVP 0\r\n"
+                                "%s",
+                                offer_crypto_attr);
+
+  sdp_parser_offer = sdp_parse (&home, offer_full, -1, sdp_f_config);
+
+  sdp_offer = sdp_session (sdp_parser_offer);
+
+
+  g_assert_true (calls_sdp_crypto_context_set_remote_media (ctx, sdp_offer->sdp_media));
+
+  answer_key = calls_srtp_generate_key_salt (30);
+  answer_key_b64 = g_base64_encode (answer_key, 30);
+  g_debug ("Answer key: %s", answer_key_b64);
+
+  answer_crypto_attr = g_strdup_printf ("a=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:%s",
+                                        answer_key_b64);
+  answer_full = g_strdup_printf ("m=audio 42000 RTP/SAVP 0\r\n"
+                                 "%s",
+                                 answer_crypto_attr);
+
+  sdp_parser_answer = sdp_parse (&home, answer_full, -1, sdp_f_config);
+  sdp_answer = sdp_session (sdp_parser_answer);
+
+  g_assert_true (calls_sdp_crypto_context_set_local_media (ctx, sdp_answer->sdp_media));
+
+  g_assert_true (calls_sdp_crypto_context_get_is_negotiated (ctx));
+
+  offer_attr = calls_sdp_crypto_context_get_remote_crypto (ctx);
+  g_assert_true (offer_attr);
+  g_assert_cmpuint (offer_attr->n_key_params, ==, 1);
+  g_assert_cmpstr (offer_attr->key_params[0].b64_keysalt, ==, offer_key_b64);
+
+  answer_attr = calls_sdp_crypto_context_get_local_crypto (ctx);
+  g_assert_true (answer_attr);
+  g_assert_cmpuint (answer_attr->n_key_params, ==, 1);
+  g_assert_cmpstr (answer_attr->key_params[0].b64_keysalt, ==, answer_key_b64);
+
+  g_assert_cmpint (offer_attr->tag, ==, answer_attr->tag);
+  g_assert_cmpint (offer_attr->crypto_suite, ==, answer_attr->crypto_suite);
+
+  sdp_parser_free (sdp_parser_offer);
+  sdp_parser_free (sdp_parser_answer);
+
+  su_home_deinit (&home);
+}
+
+
+static void
+test_crypto_offer_answer (void)
+{
+  g_autoptr (CallsSdpCryptoContext) ctx_offer = calls_sdp_crypto_context_new ();
+  g_autoptr (CallsSdpCryptoContext) ctx_answer = calls_sdp_crypto_context_new ();
+  g_autoptr (GList) attr_offer_list = NULL;
+  calls_srtp_crypto_attribute *attr_offer;
+  calls_srtp_crypto_attribute *attr_answer;
+  g_autofree char *attr_offer_str = NULL;
+  g_autofree char *attr_answer_str = NULL;
+  g_autofree char *media_line_offer = NULL;
+  g_autofree char *media_line_answer = NULL;
+  su_home_t home;
+  sdp_parser_t *sdp_parser_offer;
+  sdp_parser_t *sdp_parser_answer;
+  sdp_session_t *sdp_session_offer;
+  sdp_session_t *sdp_session_answer;
+
+  su_home_init (&home);
+
+  g_assert_cmpint (calls_sdp_crypto_context_get_state (ctx_offer), ==,
+                   CALLS_CRYPTO_CONTEXT_STATE_INIT);
+  g_assert_cmpint (calls_sdp_crypto_context_get_state (ctx_answer), ==,
+                   CALLS_CRYPTO_CONTEXT_STATE_INIT);
+
+  calls_sdp_crypto_context_generate_offer (ctx_offer);
+  g_assert_cmpint (calls_sdp_crypto_context_get_state (ctx_offer), ==,
+                   CALLS_CRYPTO_CONTEXT_STATE_OFFER_LOCAL);
+
+  attr_offer_list =
+    calls_sdp_crypto_context_get_crypto_candidates (ctx_offer, FALSE);
+  attr_offer = attr_offer_list->data;
+  attr_offer_str = calls_srtp_print_sdp_crypto_attribute (attr_offer, NULL);
+
+  g_assert_true (attr_offer_str);
+
+  media_line_offer = g_strdup_printf ("m=audio 42024 RTP/SAVP 0\r\n%s",
+                                      attr_offer_str);
+  sdp_parser_offer = sdp_parse (&home, media_line_offer, -1, sdp_f_config);
+  sdp_session_offer = sdp_session (sdp_parser_offer);
+
+  calls_sdp_crypto_context_set_remote_media (ctx_answer,
+                                             sdp_session_offer->sdp_media);
+
+  g_assert_cmpint (calls_sdp_crypto_context_get_state (ctx_answer), ==,
+                   CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE);
+
+  calls_sdp_crypto_context_generate_answer (ctx_answer);
+
+  g_assert_cmpint (calls_sdp_crypto_context_get_state (ctx_answer), ==,
+                   CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS);
+  g_assert_true (calls_sdp_crypto_context_get_is_negotiated (ctx_answer));
+
+  attr_answer = calls_sdp_crypto_context_get_remote_crypto (ctx_answer);
+  attr_answer_str = calls_srtp_print_sdp_crypto_attribute (attr_answer, NULL);
+
+  g_assert_true (attr_answer_str);
+
+  media_line_answer = g_strdup_printf ("m=audio 42124 RTP/SAVP 0\r\n%s",
+                                       attr_answer_str);
+  sdp_parser_answer = sdp_parse (&home, media_line_answer, -1, sdp_f_config);
+  sdp_session_answer = sdp_session (sdp_parser_answer);
+
+  calls_sdp_crypto_context_set_remote_media (ctx_offer,
+                                             sdp_session_answer->sdp_media);
+
+  g_assert_cmpint (calls_sdp_crypto_context_get_state (ctx_offer), ==,
+                   CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS);
+  g_assert_true (calls_sdp_crypto_context_get_is_negotiated (ctx_offer));
+
+  su_home_deinit (&home);
+}
+
+
+int
+main (int   argc,
+      char *argv[])
+{
+  gtk_test_init (&argc, &argv, NULL);
+
+  g_test_add_func ("/Calls/SDP_crypto/manual", test_crypto_manual);
+  g_test_add_func ("/Calls/SDP_crypto/offer_answer", test_crypto_offer_answer);
+
+  return g_test_run ();
+}


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