/* * GStreamer - GStreamer SRTP encoder * * Copyright 2009-2011 Collabora Ltd. * @author: Gabriel Millaire * @author: Olivier Crete * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * * Alternatively, the contents of this file may be used under the * GNU Lesser General Public License Version 2.1 (the "LGPL"), in * which case the following provisions apply instead of the ones * mentioned above: * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /** * SECTION:element-srtpenc * @title: srtpenc * @see_also: srtpdec * * gstrtpenc acts as an encoder that adds security to RTP and RTCP * packets in the form of encryption and authentication. It outs SRTP * and SRTCP. * * An application can request multiple RTP and RTCP pads to protect, * but every sink pad requested must receive packets from the same * source (identical SSRC). If a packet received contains a different * SSRC, a warning is emited and the valid SSRC is forced on the packet. * * This element uses libsrtp library. When receiving the first packet, * the library is initialized with a new stream (based on the SSRC). It * uses the default RTP and RTCP encryption and authentication mechanisms, * unless the user has set the relevant properties first. It also uses * a master key that MUST be set by property (key) at the beginning. The * master key must be of a maximum length of 46 characters (14 characters * for the salt plus the key). The encryption and authentication mecanisms * available are : * * Encryption (properties rtp-cipher and rtcp-cipher) * - AES_ICM 256 bits (maximum security) * - AES_ICM 128 bits (default) * - NULL * * Authentication (properties rtp-auth and rtcp-auth) * - HMAC_SHA1 80 bits (default, maximum protection) * - HMAC_SHA1 32 bits * - NULL * * Note that for SRTP protection, authentication is mandatory (non-null) * if encryption is used (non-null). * * When requested to create a sink pad, a linked source pad is created. * Each packet received is first analysed (checked for valid SSRC) then * its buffer is protected with libsrtp, then pushed on the source pad. * If protection failed or the stream could not be created, the buffer * is dropped and a warning is emitted. The packets pushed on the source * pad are of type 'application/x-srtp' or 'application/x-srtcp'. * * When the maximum usage of the master key is reached, a soft-limit * signal is sent to the user. The user must then set a new master key * by property. If the hard limit is reached, a flag is set and every * subsequent packet is dropped, until a new key is set and the stream * has been updated. * * If a stream is to be shared between multiple clients it is also * possible to request the internal SRTP rollover counter for a given * SSRC. The rollover counter should be then transmitted and used by the * clients to authenticate and decrypt the packets. Failing to do that * the clients will start with a rollover counter of 0 which will * probably be incorrect if the stream has been transmitted for a * while to other clients. */ #include "gstsrtpenc.h" #include #include #include #include GST_DEBUG_CATEGORY_STATIC (gst_srtp_enc_debug); #define GST_CAT_DEFAULT gst_srtp_enc_debug /* 128 bit key size: 14 (salt) + 16 */ #define MASTER_128_KEY_SIZE 30 /* 256 bit key size: 14 (salt) + 16 + 16 */ #define MASTER_256_KEY_SIZE 46 /* Properties default values */ #define DEFAULT_MASTER_KEY NULL #define DEFAULT_RTP_CIPHER GST_SRTP_CIPHER_AES_128_ICM #define DEFAULT_RTP_AUTH GST_SRTP_AUTH_HMAC_SHA1_80 #define DEFAULT_RTCP_CIPHER DEFAULT_RTP_CIPHER #define DEFAULT_RTCP_AUTH DEFAULT_RTP_AUTH #define DEFAULT_RANDOM_KEY FALSE #define DEFAULT_REPLAY_WINDOW_SIZE 128 #define DEFAULT_ALLOW_REPEAT_TX FALSE #define HAS_CRYPTO(filter) (filter->rtp_cipher != GST_SRTP_CIPHER_NULL || \ filter->rtcp_cipher != GST_SRTP_CIPHER_NULL || \ filter->rtp_auth != GST_SRTP_AUTH_NULL || \ filter->rtcp_auth != GST_SRTP_AUTH_NULL) /* Filter signals and args */ enum { SIGNAL_SOFT_LIMIT, SIGNAL_GET_ROLLOVER_COUNTER, LAST_SIGNAL }; enum { PROP_0, PROP_MKEY, PROP_RTP_CIPHER, PROP_RTP_AUTH, PROP_RTCP_CIPHER, PROP_RTCP_AUTH, PROP_RANDOM_KEY, PROP_REPLAY_WINDOW_SIZE, PROP_ALLOW_REPEAT_TX, PROP_STATS }; typedef struct ProcessBufferItData { GstSrtpEnc *filter; GstPad *pad; GstBufferList *out_list; GstFlowReturn flowret; gboolean is_rtcp; } ProcessBufferItData; /* the capabilities of the inputs and outputs. * * describe the real formats here. */ static GstStaticPadTemplate rtp_sink_template = GST_STATIC_PAD_TEMPLATE ("rtp_sink_%u", GST_PAD_SINK, GST_PAD_REQUEST, GST_STATIC_CAPS ("application/x-rtp") ); static GstStaticPadTemplate rtp_src_template = GST_STATIC_PAD_TEMPLATE ("rtp_src_%u", GST_PAD_SRC, GST_PAD_SOMETIMES, GST_STATIC_CAPS ("application/x-srtp") ); static GstStaticPadTemplate rtcp_sink_template = GST_STATIC_PAD_TEMPLATE ("rtcp_sink_%u", GST_PAD_SINK, GST_PAD_REQUEST, GST_STATIC_CAPS ("application/x-rtcp") ); static GstStaticPadTemplate rtcp_src_template = GST_STATIC_PAD_TEMPLATE ("rtcp_src_%u", GST_PAD_SRC, GST_PAD_SOMETIMES, GST_STATIC_CAPS ("application/x-srtcp") ); G_DEFINE_TYPE (GstSrtpEnc, gst_srtp_enc, GST_TYPE_ELEMENT); static guint gst_srtp_enc_signals[LAST_SIGNAL] = { 0 }; static void gst_srtp_enc_dispose (GObject * object); static void gst_srtp_enc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_srtp_enc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static gboolean gst_srtp_enc_sink_query_rtp (GstPad * pad, GstObject * parent, GstQuery * query); static gboolean gst_srtp_enc_sink_query_rtcp (GstPad * pad, GstObject * parent, GstQuery * query); static GstIterator *gst_srtp_enc_iterate_internal_links_rtp (GstPad * pad, GstObject * parent); static GstIterator *gst_srtp_enc_iterate_internal_links_rtcp (GstPad * pad, GstObject * parent); static GstFlowReturn gst_srtp_enc_chain_rtp (GstPad * pad, GstObject * parent, GstBuffer * buf); static GstFlowReturn gst_srtp_enc_chain_rtcp (GstPad * pad, GstObject * parent, GstBuffer * buf); static GstFlowReturn gst_srtp_enc_chain_list_rtp (GstPad * pad, GstObject * parent, GstBufferList * buf); static GstFlowReturn gst_srtp_enc_chain_list_rtcp (GstPad * pad, GstObject * parent, GstBufferList * buf); static gboolean gst_srtp_enc_sink_event_rtp (GstPad * pad, GstObject * parent, GstEvent * event); static gboolean gst_srtp_enc_sink_event_rtcp (GstPad * pad, GstObject * parent, GstEvent * event); static GstStateChangeReturn gst_srtp_enc_change_state (GstElement * element, GstStateChange transition); static GstPad *gst_srtp_enc_request_new_pad (GstElement * element, GstPadTemplate * templ, const gchar * name, const GstCaps * caps); static void gst_srtp_enc_release_pad (GstElement * element, GstPad * pad); /* initialize the srtpenc's class */ static void gst_srtp_enc_class_init (GstSrtpEncClass * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; gst_element_class_add_static_pad_template (gstelement_class, &rtp_src_template); gst_element_class_add_static_pad_template (gstelement_class, &rtp_sink_template); gst_element_class_add_static_pad_template (gstelement_class, &rtcp_src_template); gst_element_class_add_static_pad_template (gstelement_class, &rtcp_sink_template); gst_element_class_set_static_metadata (gstelement_class, "SRTP encoder", "Filter/Network/SRTP", "A SRTP and SRTCP encoder", "Gabriel Millaire "); /* Install callbacks */ gobject_class->set_property = gst_srtp_enc_set_property; gobject_class->get_property = gst_srtp_enc_get_property; gobject_class->dispose = gst_srtp_enc_dispose; gstelement_class->request_new_pad = GST_DEBUG_FUNCPTR (gst_srtp_enc_request_new_pad); gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_srtp_enc_release_pad); gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_srtp_enc_change_state); /* Install properties */ g_object_class_install_property (gobject_class, PROP_MKEY, g_param_spec_boxed ("key", "Key", "Master key (minimum of " G_STRINGIFY (MASTER_128_KEY_SIZE) " and maximum of " G_STRINGIFY (MASTER_256_KEY_SIZE) " bytes)", GST_TYPE_BUFFER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_PLAYING)); g_object_class_install_property (gobject_class, PROP_RTP_CIPHER, g_param_spec_enum ("rtp-cipher", "RTP Cipher", "RTP Cipher", GST_TYPE_SRTP_CIPHER_TYPE, DEFAULT_RTP_CIPHER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_RTP_AUTH, g_param_spec_enum ("rtp-auth", "RTP Authentication", "RTP Authentication", GST_TYPE_SRTP_AUTH_TYPE, DEFAULT_RTP_AUTH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_RTCP_CIPHER, g_param_spec_enum ("rtcp-cipher", "RTCP Cipher", "RTCP Cipher", GST_TYPE_SRTP_CIPHER_TYPE, DEFAULT_RTCP_CIPHER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_RTCP_AUTH, g_param_spec_enum ("rtcp-auth", "RTCP Authentication", "RTCP Authentication", GST_TYPE_SRTP_AUTH_TYPE, DEFAULT_RTCP_AUTH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_RANDOM_KEY, g_param_spec_boolean ("random-key", "Generate random key", "Generate a random key if TRUE", DEFAULT_RANDOM_KEY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_REPLAY_WINDOW_SIZE, g_param_spec_uint ("replay-window-size", "Replay window size", "Size of the replay protection window", 64, 0x8000, DEFAULT_REPLAY_WINDOW_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_ALLOW_REPEAT_TX, g_param_spec_boolean ("allow-repeat-tx", "Allow repeat packets transmission", "Whether retransmissions of packets with the same sequence number are allowed" "(Note that such repeated transmissions must have the same RTP payload, " "or a severe security weakness is introduced!)", DEFAULT_ALLOW_REPEAT_TX, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_STATS, g_param_spec_boxed ("stats", "Statistics", "Various statistics", GST_TYPE_STRUCTURE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * GstSrtpEnc::soft-limit: * @gstsrtpenc: the element on which the signal is emitted * * Signal emited when the stream with @ssrc has reached the soft * limit of utilisation of it's master encryption key. User should * provide a new key by setting the #GstSrtpEnc:key property. */ gst_srtp_enc_signals[SIGNAL_SOFT_LIMIT] = g_signal_new ("soft-limit", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } /* initialize the new element */ static void gst_srtp_enc_init (GstSrtpEnc * filter) { filter->key_changed = TRUE; filter->first_session = TRUE; filter->key = DEFAULT_MASTER_KEY; filter->rtp_cipher = DEFAULT_RTP_CIPHER; filter->rtp_auth = DEFAULT_RTP_AUTH; filter->rtcp_cipher = DEFAULT_RTCP_CIPHER; filter->rtcp_auth = DEFAULT_RTCP_AUTH; filter->replay_window_size = DEFAULT_REPLAY_WINDOW_SIZE; filter->allow_repeat_tx = DEFAULT_ALLOW_REPEAT_TX; filter->ssrcs_set = g_hash_table_new (g_direct_hash, g_direct_equal); } static guint max_cipher_key_size (GstSrtpEnc * filter) { guint rtp_size, rtcp_size; rtp_size = cipher_key_size (filter->rtp_cipher); rtcp_size = cipher_key_size (filter->rtcp_cipher); return (rtp_size > rtcp_size) ? rtp_size : rtcp_size; } /* Create stream * * Should be called with the filter locked */ static srtp_err_status_t gst_srtp_enc_create_session (GstSrtpEnc * filter) { srtp_err_status_t ret; srtp_policy_t policy; GstMapInfo map; guchar tmp[1]; memset (&policy, 0, sizeof (srtp_policy_t)); if (HAS_CRYPTO (filter)) { guint expected; gsize keysize; if (filter->key == NULL) { GST_OBJECT_UNLOCK (filter); GST_ELEMENT_ERROR (filter, LIBRARY, SETTINGS, ("Cipher is not NULL, key must be set"), ("Cipher is not NULL, key must be set")); GST_OBJECT_LOCK (filter); return srtp_err_status_fail; } expected = max_cipher_key_size (filter); keysize = gst_buffer_get_size (filter->key); if (expected != keysize) { GST_OBJECT_UNLOCK (filter); GST_ELEMENT_ERROR (filter, LIBRARY, SETTINGS, ("Master key size is wrong"), ("Expected master key of %d bytes, but received %" G_GSIZE_FORMAT " bytes", expected, keysize)); GST_OBJECT_LOCK (filter); return srtp_err_status_fail; } } GST_DEBUG_OBJECT (filter, "Setting RTP/RTCP policy to %d / %d", filter->rtp_cipher, filter->rtcp_cipher); set_crypto_policy_cipher_auth (filter->rtp_cipher, filter->rtp_auth, &policy.rtp); set_crypto_policy_cipher_auth (filter->rtcp_cipher, filter->rtcp_auth, &policy.rtcp); if (HAS_CRYPTO (filter)) { gst_buffer_map (filter->key, &map, GST_MAP_READ); policy.key = (guchar *) map.data; } else { policy.key = tmp; } policy.ssrc.value = 0; policy.ssrc.type = ssrc_any_outbound; policy.next = NULL; policy.window_size = filter->replay_window_size; policy.allow_repeat_tx = filter->allow_repeat_tx; /* If it is the first stream, create the session * If not, add the stream to the session */ ret = srtp_create (&filter->session, &policy); filter->first_session = FALSE; if (HAS_CRYPTO (filter)) gst_buffer_unmap (filter->key, &map); return ret; } /* Release ressources and set default values */ static void gst_srtp_enc_reset_no_lock (GstSrtpEnc * filter) { if (!filter->first_session) { srtp_dealloc (filter->session); filter->session = NULL; g_hash_table_remove_all (filter->ssrcs_set); } filter->first_session = TRUE; filter->key_changed = FALSE; } static void gst_srtp_enc_reset (GstSrtpEnc * filter) { GST_OBJECT_LOCK (filter); gst_srtp_enc_reset_no_lock (filter); GST_OBJECT_UNLOCK (filter); } /* Create sinkpad to receive RTP packets from encers * and a srcpad for the RTP packets */ static GstPad * create_rtp_sink (GstSrtpEnc * filter, const gchar * name) { GstPad *sinkpad, *srcpad; gchar *sinkpadname, *srcpadname; guint nb = 0; GST_DEBUG_OBJECT (filter, "creating RTP sink pad"); sinkpad = gst_pad_new_from_static_template (&rtp_sink_template, name); sinkpadname = gst_pad_get_name (sinkpad); sscanf (sinkpadname, "rtp_sink_%u", &nb); srcpadname = g_strdup_printf ("rtp_src_%u", nb); GST_DEBUG_OBJECT (filter, "creating RTP source pad"); srcpad = gst_pad_new_from_static_template (&rtp_src_template, srcpadname); g_free (srcpadname); g_free (sinkpadname); gst_pad_set_element_private (sinkpad, srcpad); gst_pad_set_element_private (srcpad, sinkpad); gst_pad_set_query_function (sinkpad, GST_DEBUG_FUNCPTR (gst_srtp_enc_sink_query_rtp)); gst_pad_set_iterate_internal_links_function (sinkpad, GST_DEBUG_FUNCPTR (gst_srtp_enc_iterate_internal_links_rtp)); gst_pad_set_chain_function (sinkpad, GST_DEBUG_FUNCPTR (gst_srtp_enc_chain_rtp)); gst_pad_set_chain_list_function (sinkpad, GST_DEBUG_FUNCPTR (gst_srtp_enc_chain_list_rtp)); gst_pad_set_event_function (sinkpad, GST_DEBUG_FUNCPTR (gst_srtp_enc_sink_event_rtp)); gst_pad_set_active (sinkpad, TRUE); gst_element_add_pad (GST_ELEMENT_CAST (filter), sinkpad); gst_pad_set_iterate_internal_links_function (srcpad, GST_DEBUG_FUNCPTR (gst_srtp_enc_iterate_internal_links_rtp)); gst_pad_set_active (srcpad, TRUE); gst_element_add_pad (GST_ELEMENT_CAST (filter), srcpad); return sinkpad; } /* Create sinkpad to receive RTCP packets from encers * and a srcpad for the RTCP packets */ static GstPad * create_rtcp_sink (GstSrtpEnc * filter, const gchar * name) { GstPad *srcpad, *sinkpad; gchar *sinkpadname, *srcpadname; guint nb = 0; GST_DEBUG_OBJECT (filter, "creating RTCP sink pad"); sinkpad = gst_pad_new_from_static_template (&rtcp_sink_template, name); sinkpadname = gst_pad_get_name (sinkpad); sscanf (sinkpadname, "rtcp_sink_%u", &nb); srcpadname = g_strdup_printf ("rtcp_src_%u", nb); GST_DEBUG_OBJECT (filter, "creating RTCP source pad"); srcpad = gst_pad_new_from_static_template (&rtcp_src_template, srcpadname); g_free (srcpadname); g_free (sinkpadname); gst_pad_set_element_private (sinkpad, srcpad); gst_pad_set_element_private (srcpad, sinkpad); gst_pad_set_query_function (sinkpad, GST_DEBUG_FUNCPTR (gst_srtp_enc_sink_query_rtcp)); gst_pad_set_iterate_internal_links_function (sinkpad, GST_DEBUG_FUNCPTR (gst_srtp_enc_iterate_internal_links_rtcp)); gst_pad_set_chain_function (sinkpad, GST_DEBUG_FUNCPTR (gst_srtp_enc_chain_rtcp)); gst_pad_set_chain_list_function (sinkpad, GST_DEBUG_FUNCPTR (gst_srtp_enc_chain_list_rtcp)); gst_pad_set_event_function (sinkpad, GST_DEBUG_FUNCPTR (gst_srtp_enc_sink_event_rtcp)); gst_pad_set_active (sinkpad, TRUE); gst_element_add_pad (GST_ELEMENT_CAST (filter), sinkpad); gst_pad_set_iterate_internal_links_function (srcpad, GST_DEBUG_FUNCPTR (gst_srtp_enc_iterate_internal_links_rtcp)); gst_pad_set_active (srcpad, TRUE); gst_element_add_pad (GST_ELEMENT_CAST (filter), srcpad); return sinkpad; } /* Handling new pad request */ static GstPad * gst_srtp_enc_request_new_pad (GstElement * element, GstPadTemplate * templ, const gchar * name, const GstCaps * caps) { GstElementClass *klass; GstSrtpEnc *filter; filter = GST_SRTP_ENC (element); klass = GST_ELEMENT_GET_CLASS (element); GST_INFO_OBJECT (element, "New pad requested"); if (templ == gst_element_class_get_pad_template (klass, "rtp_sink_%u")) return create_rtp_sink (filter, name); if (templ == gst_element_class_get_pad_template (klass, "rtcp_sink_%u")) return create_rtcp_sink (filter, name); GST_ERROR_OBJECT (element, "Could not find specified template"); return NULL; } /* Dispose */ static void gst_srtp_enc_dispose (GObject * object) { GstSrtpEnc *filter = GST_SRTP_ENC (object); GstIterator *it; GValue val = { 0 }; GST_DEBUG_OBJECT (object, "Dispose..."); it = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (object)); while (gst_iterator_next (it, &val) == GST_ITERATOR_OK) { gst_srtp_enc_release_pad (GST_ELEMENT_CAST (object), g_value_get_object (&val)); g_value_unset (&val); gst_iterator_resync (it); } gst_iterator_free (it); if (filter->key) gst_buffer_unref (filter->key); filter->key = NULL; if (filter->ssrcs_set) g_hash_table_unref (filter->ssrcs_set); filter->ssrcs_set = NULL; G_OBJECT_CLASS (gst_srtp_enc_parent_class)->dispose (object); } static GstStructure * gst_srtp_enc_create_stats (GstSrtpEnc * filter) { GstStructure *s; GValue va = G_VALUE_INIT; GValue v = G_VALUE_INIT; s = gst_structure_new_empty ("application/x-srtp-encoder-stats"); g_value_init (&va, GST_TYPE_ARRAY); g_value_init (&v, GST_TYPE_STRUCTURE); if (filter->session) { GHashTableIter iter; gpointer key; g_hash_table_iter_init (&iter, filter->ssrcs_set); while (g_hash_table_iter_next (&iter, &key, NULL)) { GstStructure *ss; guint32 ssrc = GPOINTER_TO_UINT (key); srtp_err_status_t status; guint32 roc; status = srtp_get_stream_roc (filter->session, ssrc, &roc); if (status != srtp_err_status_ok) { continue; } ss = gst_structure_new ("application/x-srtp-stream", "ssrc", G_TYPE_UINT, ssrc, "roc", G_TYPE_UINT, roc, NULL); g_value_take_boxed (&v, ss); gst_value_array_append_value (&va, &v); } } gst_structure_take_value (s, "streams", &va); g_value_unset (&v); return s; } static void gst_srtp_enc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstSrtpEnc *filter = GST_SRTP_ENC (object); GST_OBJECT_LOCK (filter); switch (prop_id) { case PROP_MKEY: if (filter->key) gst_buffer_unref (filter->key); filter->key = g_value_dup_boxed (value); filter->key_changed = TRUE; GST_INFO_OBJECT (object, "Set property: key=[%p]", filter->key); break; case PROP_RTP_CIPHER: filter->rtp_cipher = g_value_get_enum (value); GST_INFO_OBJECT (object, "Set property: rtp cipher=%d", filter->rtp_cipher); break; case PROP_RTP_AUTH: filter->rtp_auth = g_value_get_enum (value); GST_INFO_OBJECT (object, "Set property: rtp auth=%d", filter->rtp_auth); break; case PROP_RTCP_CIPHER: filter->rtcp_cipher = g_value_get_enum (value); GST_INFO_OBJECT (object, "Set property: rtcp cipher=%d", filter->rtcp_cipher); break; case PROP_RTCP_AUTH: filter->rtcp_auth = g_value_get_enum (value); GST_INFO_OBJECT (object, "Set property: rtcp auth=%d", filter->rtcp_auth); break; case PROP_RANDOM_KEY: filter->random_key = g_value_get_boolean (value); break; case PROP_REPLAY_WINDOW_SIZE: filter->replay_window_size = g_value_get_uint (value); break; case PROP_ALLOW_REPEAT_TX: filter->allow_repeat_tx = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } GST_OBJECT_UNLOCK (filter); } static void gst_srtp_enc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstSrtpEnc *filter = GST_SRTP_ENC (object); GST_OBJECT_LOCK (filter); switch (prop_id) { case PROP_MKEY: if (filter->key) g_value_set_boxed (value, filter->key); break; case PROP_RTP_CIPHER: g_value_set_enum (value, filter->rtp_cipher); break; case PROP_RTCP_CIPHER: g_value_set_enum (value, filter->rtcp_cipher); break; case PROP_RTP_AUTH: g_value_set_enum (value, filter->rtp_auth); break; case PROP_RTCP_AUTH: g_value_set_enum (value, filter->rtcp_auth); break; case PROP_RANDOM_KEY: g_value_set_boolean (value, filter->random_key); break; case PROP_REPLAY_WINDOW_SIZE: g_value_set_uint (value, filter->replay_window_size); break; case PROP_ALLOW_REPEAT_TX: g_value_set_boolean (value, filter->allow_repeat_tx); break; case PROP_STATS: g_value_take_boxed (value, gst_srtp_enc_create_stats (filter)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } GST_OBJECT_UNLOCK (filter); } /* Returns the source pad linked with the sink pad */ static GstPad * get_rtp_other_pad (GstPad * pad) { return GST_PAD (gst_pad_get_element_private (pad)); } /* Release a sink pad and it's linked source pad */ static void gst_srtp_enc_release_pad (GstElement * element, GstPad * sinkpad) { GstPad *srcpad; GST_INFO_OBJECT (element, "Releasing pad %s:%s", GST_DEBUG_PAD_NAME (sinkpad)); srcpad = GST_PAD (gst_pad_get_element_private (sinkpad)); gst_pad_set_element_private (sinkpad, NULL); gst_pad_set_element_private (srcpad, NULL); /* deactivate from source to sink */ gst_pad_set_active (srcpad, FALSE); gst_pad_set_active (sinkpad, FALSE); /* remove pads */ gst_element_remove_pad (element, srcpad); gst_element_remove_pad (element, sinkpad); } /* Common setcaps function * Handles the link with other elements */ static gboolean gst_srtp_enc_sink_setcaps (GstPad * pad, GstSrtpEnc * filter, GstCaps * caps, gboolean is_rtcp) { GstPad *otherpad = NULL; GstStructure *ps = NULL; gboolean ret = FALSE; g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE); caps = gst_caps_copy (caps); ps = gst_caps_get_structure (caps, 0); GST_DEBUG_OBJECT (pad, "Sink caps: %" GST_PTR_FORMAT, caps); if (is_rtcp) gst_structure_set_name (ps, "application/x-srtcp"); else gst_structure_set_name (ps, "application/x-srtp"); GST_OBJECT_LOCK (filter); if (gst_structure_has_field_typed (ps, "ssrc", G_TYPE_UINT)) { guint ssrc; gst_structure_get_uint (ps, "ssrc", &ssrc); g_hash_table_add (filter->ssrcs_set, GUINT_TO_POINTER (ssrc)); } if (HAS_CRYPTO (filter)) gst_structure_set (ps, "srtp-key", GST_TYPE_BUFFER, filter->key, NULL); /* Add srtp-specific params to source caps */ gst_structure_set (ps, "srtp-cipher", G_TYPE_STRING, enum_nick_from_value (GST_TYPE_SRTP_CIPHER_TYPE, filter->rtp_cipher), "srtp-auth", G_TYPE_STRING, enum_nick_from_value (GST_TYPE_SRTP_AUTH_TYPE, filter->rtp_auth), "srtcp-cipher", G_TYPE_STRING, enum_nick_from_value (GST_TYPE_SRTP_CIPHER_TYPE, filter->rtcp_cipher), "srtcp-auth", G_TYPE_STRING, enum_nick_from_value (GST_TYPE_SRTP_AUTH_TYPE, filter->rtcp_auth), NULL); GST_OBJECT_UNLOCK (filter); GST_DEBUG_OBJECT (pad, "Source caps: %" GST_PTR_FORMAT, caps); /* Set caps on source pad */ otherpad = get_rtp_other_pad (pad); ret = gst_pad_set_caps (otherpad, caps); gst_caps_unref (caps); return ret; } static gboolean gst_srtp_enc_sink_query (GstPad * pad, GstObject * parent, GstQuery * query, gboolean is_rtcp) { switch (GST_QUERY_TYPE (query)) { case GST_QUERY_CAPS: { GstCaps *filter = NULL; GstCaps *other_filter = NULL; GstPad *otherpad; GstCaps *other_caps; GstCaps *ret; GstCaps *template_caps; int i; otherpad = get_rtp_other_pad (pad); gst_query_parse_caps (query, &filter); if (filter) { other_filter = gst_caps_copy (filter); for (i = 0; i < gst_caps_get_size (other_filter); i++) { GstStructure *ps = gst_caps_get_structure (other_filter, i); if (is_rtcp) gst_structure_set_name (ps, "application/x-srtcp"); else gst_structure_set_name (ps, "application/x-srtp"); } } other_caps = gst_pad_peer_query_caps (otherpad, other_filter); if (other_filter) gst_caps_unref (other_filter); if (!other_caps) goto return_template; template_caps = gst_pad_get_pad_template_caps (otherpad); ret = gst_caps_intersect_full (other_caps, template_caps, GST_CAPS_INTERSECT_FIRST); gst_caps_unref (other_caps); gst_caps_unref (template_caps); ret = gst_caps_make_writable (ret); for (i = 0; i < gst_caps_get_size (ret); i++) { GstStructure *ps = gst_caps_get_structure (ret, i); if (is_rtcp) gst_structure_set_name (ps, "application/x-rtcp"); else gst_structure_set_name (ps, "application/x-rtp"); gst_structure_remove_fields (ps, "srtp-key", "srtp-cipher", "srtp-auth", "srtcp-cipher", "srtcp-auth", NULL); } gst_query_set_caps_result (query, ret); gst_caps_unref (ret); return TRUE; return_template: ret = gst_pad_get_pad_template_caps (pad); gst_query_set_caps_result (query, ret); gst_caps_unref (ret); return TRUE; } default: return gst_pad_query_default (pad, parent, query); } } static gboolean gst_srtp_enc_sink_query_rtp (GstPad * pad, GstObject * parent, GstQuery * query) { return gst_srtp_enc_sink_query (pad, parent, query, FALSE); } static gboolean gst_srtp_enc_sink_query_rtcp (GstPad * pad, GstObject * parent, GstQuery * query) { return gst_srtp_enc_sink_query (pad, parent, query, TRUE); } static GstIterator * gst_srtp_enc_iterate_internal_links (GstPad * pad, GstObject * parent, gboolean is_rtcp) { GstSrtpEnc *filter = GST_SRTP_ENC (parent); GstPad *otherpad = NULL; GstIterator *it = NULL; otherpad = get_rtp_other_pad (pad); if (otherpad) { GValue val = { 0 }; g_value_init (&val, GST_TYPE_PAD); g_value_set_object (&val, otherpad); it = gst_iterator_new_single (GST_TYPE_PAD, &val); g_value_unset (&val); } else { GST_ELEMENT_ERROR (GST_ELEMENT_CAST (filter), CORE, PAD, (NULL), ("Unable to get linked pad")); } return it; } static GstIterator * gst_srtp_enc_iterate_internal_links_rtp (GstPad * pad, GstObject * parent) { return gst_srtp_enc_iterate_internal_links (pad, parent, FALSE); } static GstIterator * gst_srtp_enc_iterate_internal_links_rtcp (GstPad * pad, GstObject * parent) { return gst_srtp_enc_iterate_internal_links (pad, parent, TRUE); } static void gst_srtp_enc_replace_random_key (GstSrtpEnc * filter) { guint i; guint key_size; GstMapInfo map; GST_DEBUG_OBJECT (filter, "Generating random key"); if (filter->key) gst_buffer_unref (filter->key); key_size = max_cipher_key_size (filter); filter->key = gst_buffer_new_allocate (NULL, key_size, NULL); gst_buffer_map (filter->key, &map, GST_MAP_WRITE); for (i = 0; i < map.size; i += 4) GST_WRITE_UINT32_BE (map.data + i, g_random_int ()); gst_buffer_unmap (filter->key, &map); filter->key_changed = TRUE; } static GstFlowReturn gst_srtp_enc_check_set_caps (GstSrtpEnc * filter, GstPad * pad, gboolean is_rtcp) { gboolean do_setcaps = FALSE; GST_OBJECT_LOCK (filter); if (filter->key_changed) { gst_srtp_enc_reset_no_lock (filter); do_setcaps = TRUE; } if (filter->first_session) { srtp_err_status_t status = gst_srtp_enc_create_session (filter); if (status != srtp_err_status_ok) { GST_OBJECT_UNLOCK (filter); GST_ELEMENT_ERROR (filter, LIBRARY, INIT, ("Could not initialize SRTP encoder"), ("Failed to add stream to SRTP encoder (err: %d)", status)); return GST_FLOW_ERROR; } } GST_OBJECT_UNLOCK (filter); /* Update source caps if asked */ if (do_setcaps) { GstCaps *caps; caps = gst_pad_get_current_caps (pad); if (!gst_srtp_enc_sink_setcaps (pad, filter, caps, is_rtcp)) { gst_caps_unref (caps); return GST_FLOW_NOT_NEGOTIATED; } gst_caps_unref (caps); } return GST_FLOW_OK; } static GstFlowReturn gst_srtp_enc_process_buffer (GstSrtpEnc * filter, GstPad * pad, GstBuffer * buf, gboolean is_rtcp, GstBuffer ** outbuf_ptr) { GstFlowReturn ret = GST_FLOW_OK; gint size_max, size; GstBuffer *bufout = NULL; GstMapInfo mapout; srtp_err_status_t err; /* Create a bigger buffer to add protection */ size = gst_buffer_get_size (buf); size_max = size + SRTP_MAX_TRAILER_LEN + 10; bufout = gst_buffer_new_allocate (NULL, size_max, NULL); gst_buffer_map (bufout, &mapout, GST_MAP_READWRITE); gst_buffer_extract (buf, 0, mapout.data, size); GST_OBJECT_LOCK (filter); gst_srtp_init_event_reporter (); if (filter->session == NULL) { /* The rtcp session disappeared (element shutting down) */ GST_OBJECT_UNLOCK (filter); ret = GST_FLOW_FLUSHING; goto fail; } if (is_rtcp) err = srtp_protect_rtcp (filter->session, mapout.data, &size); else err = srtp_protect (filter->session, mapout.data, &size); GST_OBJECT_UNLOCK (filter); gst_buffer_unmap (bufout, &mapout); if (err == srtp_err_status_ok) { /* Buffer protected */ gst_buffer_set_size (bufout, size); gst_buffer_copy_into (bufout, buf, GST_BUFFER_COPY_METADATA, 0, -1); GST_LOG_OBJECT (pad, "Encoding %s buffer of size %d", is_rtcp ? "RTCP" : "RTP", size); } else if (err == srtp_err_status_key_expired) { GST_ELEMENT_ERROR (GST_ELEMENT_CAST (filter), STREAM, ENCODE, ("Key usage limit has been reached"), ("Unable to protect buffer (hard key usage limit reached)")); ret = GST_FLOW_ERROR; goto fail; } else { /* srtp_protect failed */ GST_ELEMENT_ERROR (filter, LIBRARY, FAILED, (NULL), ("Unable to protect buffer (protect failed) code %d", err)); ret = GST_FLOW_ERROR; goto fail; } *outbuf_ptr = bufout; return ret; fail: gst_buffer_unref (bufout); return ret; } static GstFlowReturn gst_srtp_enc_chain (GstPad * pad, GstObject * parent, GstBuffer * buf, gboolean is_rtcp) { GstSrtpEnc *filter = GST_SRTP_ENC (parent); GstFlowReturn ret = GST_FLOW_OK; GstPad *otherpad; GstBuffer *bufout = NULL; if ((ret = gst_srtp_enc_check_set_caps (filter, pad, is_rtcp)) != GST_FLOW_OK) { goto out; } GST_OBJECT_LOCK (filter); if (!HAS_CRYPTO (filter)) { GST_OBJECT_UNLOCK (filter); otherpad = get_rtp_other_pad (pad); return gst_pad_push (otherpad, buf); } GST_OBJECT_UNLOCK (filter); ret = gst_srtp_enc_process_buffer (filter, pad, buf, is_rtcp, &bufout); if (ret != GST_FLOW_OK) goto out; /* Push buffer to source pad */ otherpad = get_rtp_other_pad (pad); ret = gst_pad_push (otherpad, bufout); bufout = NULL; if (ret != GST_FLOW_OK) goto out; GST_OBJECT_LOCK (filter); if (gst_srtp_get_soft_limit_reached ()) { GST_OBJECT_UNLOCK (filter); g_signal_emit (filter, gst_srtp_enc_signals[SIGNAL_SOFT_LIMIT], 0); GST_OBJECT_LOCK (filter); if (filter->random_key && !filter->key_changed) gst_srtp_enc_replace_random_key (filter); } GST_OBJECT_UNLOCK (filter); out: gst_buffer_unref (buf); return ret; } static gboolean process_buffer_it (GstBuffer ** buffer, guint index, gpointer user_data) { ProcessBufferItData *data = user_data; GstBuffer *bufout; GstFlowReturn ret; ret = gst_srtp_enc_process_buffer (data->filter, data->pad, *buffer, data->is_rtcp, &bufout); if (ret != GST_FLOW_OK) { data->flowret = ret; return FALSE; } gst_buffer_list_add (data->out_list, bufout); return TRUE; } static GstFlowReturn gst_srtp_enc_chain_list (GstPad * pad, GstObject * parent, GstBufferList * buf_list, gboolean is_rtcp) { GstSrtpEnc *filter = GST_SRTP_ENC (parent); GstFlowReturn ret = GST_FLOW_OK; GstPad *otherpad; GstBufferList *out_list = NULL; ProcessBufferItData process_data; GST_LOG_OBJECT (pad, "Buffer chain with list of %d", gst_buffer_list_length (buf_list)); if (!gst_buffer_list_length (buf_list)) goto out; if ((ret = gst_srtp_enc_check_set_caps (filter, pad, is_rtcp)) != GST_FLOW_OK) goto out; GST_OBJECT_LOCK (filter); if (!HAS_CRYPTO (filter)) { GST_OBJECT_UNLOCK (filter); otherpad = get_rtp_other_pad (pad); return gst_pad_push_list (otherpad, buf_list); } GST_OBJECT_UNLOCK (filter); out_list = gst_buffer_list_new (); process_data.filter = filter; process_data.pad = pad; process_data.is_rtcp = is_rtcp; process_data.out_list = out_list; process_data.flowret = GST_FLOW_OK; if (!gst_buffer_list_foreach (buf_list, process_buffer_it, &process_data)) { ret = process_data.flowret; goto out; } if (!gst_buffer_list_length (out_list)) { gst_buffer_list_unref (out_list); ret = GST_FLOW_OK; goto out; } /* Push buffer to source pad */ otherpad = get_rtp_other_pad (pad); GST_LOG_OBJECT (pad, "Pushing buffer chain of %d", gst_buffer_list_length (buf_list)); ret = gst_pad_push_list (otherpad, out_list); if (ret != GST_FLOW_OK) { goto out; } GST_OBJECT_LOCK (filter); if (gst_srtp_get_soft_limit_reached ()) { GST_OBJECT_UNLOCK (filter); g_signal_emit (filter, gst_srtp_enc_signals[SIGNAL_SOFT_LIMIT], 0); GST_OBJECT_LOCK (filter); if (filter->random_key && !filter->key_changed) gst_srtp_enc_replace_random_key (filter); } GST_OBJECT_UNLOCK (filter); out: gst_buffer_list_unref (buf_list); return ret; } static GstFlowReturn gst_srtp_enc_chain_rtp (GstPad * pad, GstObject * parent, GstBuffer * buf) { return gst_srtp_enc_chain (pad, parent, buf, FALSE); } static GstFlowReturn gst_srtp_enc_chain_rtcp (GstPad * pad, GstObject * parent, GstBuffer * buf) { return gst_srtp_enc_chain (pad, parent, buf, TRUE); } static GstFlowReturn gst_srtp_enc_chain_list_rtp (GstPad * pad, GstObject * parent, GstBufferList * buf_list) { return gst_srtp_enc_chain_list (pad, parent, buf_list, FALSE); } static GstFlowReturn gst_srtp_enc_chain_list_rtcp (GstPad * pad, GstObject * parent, GstBufferList * buf_list) { return gst_srtp_enc_chain_list (pad, parent, buf_list, TRUE); } /* Change state */ static GstStateChangeReturn gst_srtp_enc_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn res; GstSrtpEnc *filter; filter = GST_SRTP_ENC (element); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: if (filter->rtp_cipher != GST_SRTP_CIPHER_NULL || filter->rtcp_cipher != GST_SRTP_CIPHER_NULL || filter->rtp_auth != GST_SRTP_AUTH_NULL || filter->rtcp_auth != GST_SRTP_AUTH_NULL) { if (!filter->key) { if (filter->random_key) { gst_srtp_enc_replace_random_key (filter); } else { GST_ERROR_OBJECT (element, "Need a key to get to READY"); return GST_STATE_CHANGE_FAILURE; } } } if ((filter->rtcp_cipher != SRTP_NULL_CIPHER) && (filter->rtcp_auth == SRTP_NULL_AUTH)) { GST_ERROR_OBJECT (filter, "RTCP authentication can't be NULL if encryption is not NULL."); return GST_STATE_CHANGE_FAILURE; } GST_OBJECT_LOCK (filter); if (!filter->first_session) gst_srtp_enc_reset_no_lock (filter); GST_OBJECT_UNLOCK (filter); break; case GST_STATE_CHANGE_READY_TO_PAUSED: break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: break; default: break; } res = GST_ELEMENT_CLASS (gst_srtp_enc_parent_class)->change_state (element, transition); switch (transition) { case GST_STATE_CHANGE_PLAYING_TO_PAUSED: break; case GST_STATE_CHANGE_PAUSED_TO_READY: gst_srtp_enc_reset (filter); break; case GST_STATE_CHANGE_READY_TO_NULL: break; default: break; } return res; } static gboolean gst_srtp_enc_sink_event (GstPad * pad, GstObject * parent, GstEvent * event, gboolean is_rtcp) { GstSrtpEnc *filter = GST_SRTP_ENC (parent); gboolean ret; GstPad *otherpad; otherpad = get_rtp_other_pad (pad); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_FLUSH_STOP: GST_DEBUG_OBJECT (pad, "Encing event Flush stop (%d)", GST_EVENT_TYPE (event)); gst_srtp_enc_reset (filter); ret = gst_pad_push_event (otherpad, event); break; case GST_EVENT_CAPS: { GstCaps *caps; gst_event_parse_caps (event, &caps); ret = gst_srtp_enc_sink_setcaps (pad, filter, caps, is_rtcp); gst_event_unref (event); break; } default: GST_DEBUG_OBJECT (pad, "Encing event default (%d)", GST_EVENT_TYPE (event)); ret = gst_pad_event_default (pad, parent, event); break; } return ret; } static gboolean gst_srtp_enc_sink_event_rtp (GstPad * pad, GstObject * parent, GstEvent * event) { return gst_srtp_enc_sink_event (pad, parent, event, FALSE); } static gboolean gst_srtp_enc_sink_event_rtcp (GstPad * pad, GstObject * parent, GstEvent * event) { return gst_srtp_enc_sink_event (pad, parent, event, TRUE); } /* entry point to initialize the plug-in * initialize the plug-in itself * register the element factories and other features */ gboolean gst_srtp_enc_plugin_init (GstPlugin * srtpenc) { GST_DEBUG_CATEGORY_INIT (gst_srtp_enc_debug, "srtpenc", 0, "SRTP Enc"); return gst_element_register (srtpenc, "srtpenc", GST_RANK_NONE, GST_TYPE_SRTP_ENC); }