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);