From e75e04fb4e96900d9d3d1b0d2778c557a64ea7f8 Mon Sep 17 00:00:00 2001 From: Evangelos Ribeiro Tzaras Date: Fri, 6 May 2022 01:21:27 +0200 Subject: [PATCH] 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 7842ef8..8ea7b7e 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 5c9c189..60c1cdf 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 #include @@ -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 582ab89..57451de 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 a87af6e..4436e19 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 47f8c09..874d7df 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);