From 5cf4dc2b82f10cbe2dc603fd49ab080ff8d32e56 Mon Sep 17 00:00:00 2001 From: Aaron Boxer Date: Tue, 6 Jul 2021 12:31:42 -0400 Subject: [PATCH] aes: add aes encryption and decryption elements Part-of: --- docs/plugins/gst_plugins_cache.json | 206 +++++++++ ext/aes/gstaes.c | 54 +++ ext/aes/gstaesdec.c | 625 ++++++++++++++++++++++++++++ ext/aes/gstaesdec.h | 90 ++++ ext/aes/gstaesenc.c | 594 ++++++++++++++++++++++++++ ext/aes/gstaesenc.h | 92 ++++ ext/aes/gstaeshelper.c | 187 +++++++++ ext/aes/gstaeshelper.h | 104 +++++ ext/aes/meson.build | 33 ++ ext/meson.build | 1 + meson_options.txt | 1 + tests/check/elements/aesdec.c | 181 ++++++++ tests/check/elements/aesenc.c | 179 ++++++++ tests/check/meson.build | 3 + 14 files changed, 2350 insertions(+) create mode 100644 ext/aes/gstaes.c create mode 100644 ext/aes/gstaesdec.c create mode 100644 ext/aes/gstaesdec.h create mode 100644 ext/aes/gstaesenc.c create mode 100644 ext/aes/gstaesenc.h create mode 100644 ext/aes/gstaeshelper.c create mode 100644 ext/aes/gstaeshelper.h create mode 100644 ext/aes/meson.build create mode 100644 tests/check/elements/aesdec.c create mode 100644 tests/check/elements/aesenc.c diff --git a/docs/plugins/gst_plugins_cache.json b/docs/plugins/gst_plugins_cache.json index 7385b7b42a..6e03b794b8 100644 --- a/docs/plugins/gst_plugins_cache.json +++ b/docs/plugins/gst_plugins_cache.json @@ -186,6 +186,212 @@ "tracers": {}, "url": "Unknown package origin" }, + "aes": { + "description": "AES encryption/decryption plugin", + "elements": { + "aesdec": { + "author": "Rabindra Harlalka ", + "description": "AES buffer decryption", + "hierarchy": [ + "GstAesDec", + "GstBaseTransform", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "klass": "Generic/Filter", + "long-name": "aesdec", + "pad-templates": { + "sink": { + "caps": "ANY", + "direction": "sink", + "presence": "always" + }, + "src": { + "caps": "ANY", + "direction": "src", + "presence": "always" + } + }, + "properties": { + "cipher": { + "blurb": "cipher mode", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "aes-128-cbc (0)", + "mutable": "ready", + "readable": true, + "type": "GstAesCipher", + "writable": true + }, + "iv": { + "blurb": "AES encryption initialization vector (in hexadecimal). Length must equal AES block length (16 bytes)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "", + "mutable": "ready", + "readable": true, + "type": "gchararray", + "writable": true + }, + "key": { + "blurb": "AES encryption key (in hexadecimal). Length (in bytes) must be equivalent to the number of bits in the key length : 16 bytes for AES 128 and 32 bytes for AES 256", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "", + "mutable": "ready", + "readable": true, + "type": "gchararray", + "writable": true + }, + "per-buffer-padding": { + "blurb": "If true, pad each buffer using PKCS7 padding scheme. Otherwise, onlypad final buffer", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "true", + "mutable": "ready", + "readable": true, + "type": "gboolean", + "writable": true + }, + "serialize-iv": { + "blurb": "Read initialization vector from first 16 bytes of first buffer", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "ready", + "readable": true, + "type": "gboolean", + "writable": true + } + }, + "rank": "primary" + }, + "aesenc": { + "author": "Rabindra Harlalka ", + "description": "AES buffer encryption", + "hierarchy": [ + "GstAesEnc", + "GstBaseTransform", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "klass": "Generic/Filter", + "long-name": "aesenc", + "pad-templates": { + "sink": { + "caps": "ANY", + "direction": "sink", + "presence": "always" + }, + "src": { + "caps": "ANY", + "direction": "src", + "presence": "always" + } + }, + "properties": { + "cipher": { + "blurb": "cipher mode", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "aes-128-cbc (0)", + "mutable": "ready", + "readable": true, + "type": "GstAesCipher", + "writable": true + }, + "iv": { + "blurb": "AES encryption initialization vector (in hexadecimal). Length must equal AES block length (16 bytes)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "", + "mutable": "ready", + "readable": true, + "type": "gchararray", + "writable": true + }, + "key": { + "blurb": "AES encryption key (in hexadecimal). Length (in bytes) must be equivalent to the number of bits in the key length : 16 bytes for AES 128 and 32 bytes for AES 256", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "", + "mutable": "ready", + "readable": true, + "type": "gchararray", + "writable": true + }, + "per-buffer-padding": { + "blurb": "If true, pad each buffer using PKCS7 padding scheme. Otherwise, onlypad final buffer", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "true", + "mutable": "ready", + "readable": true, + "type": "gboolean", + "writable": true + }, + "serialize-iv": { + "blurb": "Store initialization vector in first 16 bytes of first buffer", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "ready", + "readable": true, + "type": "gboolean", + "writable": true + } + }, + "rank": "primary" + } + }, + "filename": "gstaes", + "license": "LGPL", + "other-types": { + "GstAesCipher": { + "kind": "enum", + "values": [ + { + "desc": "AES 128 bit cipher key using CBC method", + "name": "aes-128-cbc", + "value": "0" + }, + { + "desc": "AES 256 bit cipher key using CBC method", + "name": "aes-256-cbc", + "value": "1" + } + ] + } + }, + "package": "GStreamer Bad Plug-ins", + "source": "gst-plugins-bad", + "tracers": {}, + "url": "Unknown package origin" + }, "aiff": { "description": "Create and parse Audio Interchange File Format (AIFF) files", "elements": { diff --git a/ext/aes/gstaes.c b/ext/aes/gstaes.c new file mode 100644 index 0000000000..096d032cc6 --- /dev/null +++ b/ext/aes/gstaes.c @@ -0,0 +1,54 @@ +/* + * GStreamer gstreamer-aes + * + * Copyright, 2021 Nice, Contact: Rabindra Harlalka + * + * gstaes.c + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstaesdec.h" +#include "gstaesenc.h" + +/** + * SECTION:plugin-aes + * + * AES encryption and decryption + * + * See also: @aesenc, @aesdec + * Since: 1.20 + */ + + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gboolean success = GST_ELEMENT_REGISTER (aesenc, plugin); + success = GST_ELEMENT_REGISTER (aesdec, plugin) || success; + + return success; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + aes, + "AES encryption/decryption plugin", + plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/ext/aes/gstaesdec.c b/ext/aes/gstaesdec.c new file mode 100644 index 0000000000..9383f047af --- /dev/null +++ b/ext/aes/gstaesdec.c @@ -0,0 +1,625 @@ +/* + * GStreamer gstreamer-aesdec + * + * Copyright, LCC (C) 2015 RidgeRun, LCC + * Copyright, LCC (C) 2016 RidgeRun, LCC + * Copyright (C) 2020 Nice, Contact: Rabindra Harlalka + * + * 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1335, USA. + */ + +/** + * SECTION:element-aesdec + * + * AES decryption + * + * ## Example + * + * |[ + * echo "This is an AES crypto test ... " > plain.txt && \ + * gst-launch-1.0 filesrc location=plain.txt ! \ + * aesenc key=1f9423681beb9a79215820f6bda73d0f iv=e9aa8e834d8d70b7e0d254ff670dd718 ! \ + * aesdec key=1f9423681beb9a79215820f6bda73d0f iv=e9aa8e834d8d70b7e0d254ff670dd718 ! \ + * filesink location=dec.txt && \ + * cat dec.txt + * + * ]| + * + * Since: 1.20 + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include "gstaeshelper.h" +#include "gstaesdec.h" + +GST_DEBUG_CATEGORY_STATIC (gst_aes_dec_debug); +#define GST_CAT_DEFAULT gst_aes_dec_debug +G_DEFINE_TYPE_WITH_CODE (GstAesDec, gst_aes_dec, GST_TYPE_BASE_TRANSFORM, + GST_DEBUG_CATEGORY_INIT (gst_aes_dec_debug, "aesdec", 0, + "aesdec AES decryption element") + ); +GST_ELEMENT_REGISTER_DEFINE (aesdec, "aesdec", GST_RANK_PRIMARY, + GST_TYPE_AES_DEC); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("ANY") + ); + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("ANY") + ); + +static void gst_aes_dec_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_aes_dec_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static GstFlowReturn gst_aes_dec_transform (GstBaseTransform * base, + GstBuffer * inbuf, GstBuffer * outbuf); +static GstFlowReturn gst_aes_dec_prepare_output_buffer (GstBaseTransform * base, + GstBuffer * inbuf, GstBuffer ** outbuf); +static gboolean +gst_aes_dec_sink_event (GstBaseTransform * base, GstEvent * event); + +static gboolean gst_aes_dec_start (GstBaseTransform * base); +static gboolean gst_aes_dec_stop (GstBaseTransform * base); + +/* aes_dec helper functions */ +static gboolean gst_aes_dec_openssl_init (GstAesDec * filter); +static gboolean gst_aes_dec_init_cipher (GstAesDec * filter); +static void gst_aes_dec_finalize (GObject * object); + +/* GObject vmethod implementations */ + +/* initialize class */ +static void +gst_aes_dec_class_init (GstAesDecClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->set_property = gst_aes_dec_set_property; + gobject_class->get_property = gst_aes_dec_get_property; + gobject_class->finalize = gst_aes_dec_finalize; + + gst_type_mark_as_plugin_api (GST_TYPE_AES_CIPHER, 0); + + /** + * GstAesDec:cipher + * + * AES cipher mode (key length and mode) + * Currently, 128 and 256 bit keys are supported, + * in "cipher block chaining" (CBC) mode + * + * Since: 1.20 + */ + g_object_class_install_property (gobject_class, PROP_CIPHER, + g_param_spec_enum ("cipher", + "Cipher", + "cipher mode", + GST_TYPE_AES_CIPHER, GST_AES_DEFAULT_CIPHER_MODE, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + GST_PARAM_MUTABLE_READY))); + + /** + * GstAesDec:serialize-iv + * + * If true, read initialization vector from first 16 bytes of first buffer + * + * Since: 1.20 + */ + g_object_class_install_property (gobject_class, PROP_SERIALIZE_IV, + g_param_spec_boolean ("serialize-iv", "Serialize IV", + "Read initialization vector from first 16 bytes of first buffer", + GST_AES_DEFAULT_SERIALIZE_IV, + G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY)); + + /** + * GstAesDec:per-buffer-padding + * + * If true, each buffer will be padded using PKCS7 padding + * If false, only the final buffer in the stream will be padded + * (by OpenSSL) using PKCS7 + * + * Since: 1.20 + */ + g_object_class_install_property (gobject_class, PROP_PER_BUFFER_PADDING, + g_param_spec_boolean ("per-buffer-padding", "Per buffer padding", + "If true, pad each buffer using PKCS7 padding scheme. Otherwise, only" + "pad final buffer", + GST_AES_PER_BUFFER_PADDING_DEFAULT, + G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY)); + + /** + * GstAesDec:key + * + * AES encryption key (hexadecimal) + * + * Since: 1.20 + */ + g_object_class_install_property (gobject_class, PROP_KEY, + g_param_spec_string ("key", "Key", + "AES encryption key (in hexadecimal). Length (in bytes) must be equivalent to " + "the number of bits in the key length : " + "16 bytes for AES 128 and 32 bytes for AES 256", + (gchar *) GST_AES_DEFAULT_KEY, + G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY)); + /** + * GstAesDec:iv + * + * AES encryption initialization vector (hexadecimal) + * + * Since: 1.20 + */ + g_object_class_install_property (gobject_class, PROP_IV, + g_param_spec_string ("iv", "Iv", + "AES encryption initialization vector (in hexadecimal). " + "Length must equal AES block length (16 bytes)", + (gchar *) GST_AES_DEFAULT_IV, + G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY)); + + gst_element_class_set_details_simple (gstelement_class, + "aesdec", + "Generic/Filter", + "AES buffer decryption", + "Rabindra Harlalka "); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&src_template)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&sink_template)); + + GST_BASE_TRANSFORM_CLASS (klass)->transform = + GST_DEBUG_FUNCPTR (gst_aes_dec_transform); + GST_BASE_TRANSFORM_CLASS (klass)->prepare_output_buffer = + GST_DEBUG_FUNCPTR (gst_aes_dec_prepare_output_buffer); + GST_BASE_TRANSFORM_CLASS (klass)->start = + GST_DEBUG_FUNCPTR (gst_aes_dec_start); + GST_BASE_TRANSFORM_CLASS (klass)->sink_event = + GST_DEBUG_FUNCPTR (gst_aes_dec_sink_event); + GST_BASE_TRANSFORM_CLASS (klass)->stop = GST_DEBUG_FUNCPTR (gst_aes_dec_stop); +} + +/* Initialize element + */ +static void +gst_aes_dec_init (GstAesDec * filter) +{ + GST_INFO_OBJECT (filter, "Initializing plugin"); + filter->cipher = GST_AES_DEFAULT_CIPHER_MODE; + filter->awaiting_first_buffer = TRUE; + filter->per_buffer_padding = GST_AES_PER_BUFFER_PADDING_DEFAULT; + g_mutex_init (&filter->decoder_lock); +} + +static void +gst_aes_dec_finalize (GObject * object) +{ + GstAesDec *filter = GST_AES_DEC (object); + + g_mutex_clear (&filter->decoder_lock); + G_OBJECT_CLASS (gst_aes_dec_parent_class)->finalize (object); +} + + +static void +gst_aes_dec_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstAesDec *filter = GST_AES_DEC (object); + + g_mutex_lock (&filter->decoder_lock); + /* no property may be set after first output buffer is prepared */ + if (filter->locked_properties) { + GST_WARNING_OBJECT (filter, + "Properties cannot be set once buffers begin flowing in element. Ignored"); + goto cleanup; + } + + switch (prop_id) { + case PROP_CIPHER: + filter->cipher = g_value_get_enum (value); + filter->evp_cipher = + EVP_get_cipherbyname (gst_aes_cipher_enum_to_string (filter->cipher)); + GST_DEBUG_OBJECT (filter, "cipher: %s", + gst_aes_cipher_enum_to_string (filter->cipher)); + break; + case PROP_SERIALIZE_IV: + filter->serialize_iv = g_value_get_boolean (value); + GST_DEBUG_OBJECT (filter, "serialize iv: %s", + filter->serialize_iv ? "TRUE" : "FALSE"); + break; + case PROP_PER_BUFFER_PADDING: + filter->per_buffer_padding = g_value_get_boolean (value); + GST_DEBUG_OBJECT (filter, "Per buffer padding: %s", + filter->per_buffer_padding ? "TRUE" : "FALSE"); + break; + case PROP_KEY: + { + guint hex_len = gst_aes_hexstring2bytearray (GST_ELEMENT (filter), + g_value_get_string (value), filter->key); + + if (!hex_len) { + GST_ERROR_OBJECT (filter, "invalid key"); + goto cleanup; + } + GST_DEBUG_OBJECT (filter, "key: %s", g_value_get_string (value)); + } + break; + case PROP_IV: + { + gchar iv_string[2 * GST_AES_BLOCK_SIZE + 1]; + guint hex_len = gst_aes_hexstring2bytearray (GST_ELEMENT (filter), + g_value_get_string (value), filter->iv); + + if (hex_len != GST_AES_BLOCK_SIZE) { + GST_ERROR_OBJECT (filter, "invalid initialization vector"); + goto cleanup; + } + GST_DEBUG_OBJECT (filter, "iv: %s", + gst_aes_bytearray2hexstring (filter->iv, iv_string, + GST_AES_BLOCK_SIZE)); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +cleanup: + g_mutex_unlock (&filter->decoder_lock); +} + +static void +gst_aes_dec_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstAesDec *filter = GST_AES_DEC (object); + + switch (prop_id) { + case PROP_CIPHER: + g_value_set_enum (value, filter->cipher); + break; + case PROP_SERIALIZE_IV: + g_value_set_boolean (value, filter->serialize_iv); + break; + case PROP_PER_BUFFER_PADDING: + g_value_set_boolean (value, filter->per_buffer_padding); + break; + case PROP_KEY: + g_value_set_string (value, (gchar *) filter->key); + break; + case PROP_IV: + g_value_set_string (value, (gchar *) filter->iv); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gst_aes_dec_sink_event (GstBaseTransform * base, GstEvent * event) +{ + GstAesDec *filter = GST_AES_DEC (base); + + g_mutex_lock (&filter->decoder_lock); + + if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) { + GST_DEBUG_OBJECT (filter, "Received EOS on sink pad"); + if (!filter->per_buffer_padding && !filter->awaiting_first_buffer) { + GstBuffer *outbuf = NULL; + gint len; + GstMapInfo outmap; + + outbuf = gst_buffer_new_allocate (NULL, EVP_MAX_BLOCK_LENGTH, NULL); + if (outbuf == NULL) { + GST_DEBUG_OBJECT (filter, + "Failed to allocate a new buffer of length %d", + EVP_MAX_BLOCK_LENGTH); + goto buffer_fail; + } + if (!gst_buffer_map (outbuf, &outmap, GST_MAP_WRITE)) { + GST_DEBUG_OBJECT (filter, + "gst_buffer_map on outbuf failed for final buffer."); + gst_buffer_unref (outbuf); + goto buffer_fail; + } + if (1 != EVP_CipherFinal_ex (filter->evp_ctx, outmap.data, &len)) { + GST_DEBUG_OBJECT (filter, "Could not finalize openssl encryption"); + gst_buffer_unmap (outbuf, &outmap); + gst_buffer_unref (outbuf); + goto cipher_fail; + } + if (len == 0) { + GST_DEBUG_OBJECT (filter, "Not pushing final buffer as length is 0"); + gst_buffer_unmap (outbuf, &outmap); + gst_buffer_unref (outbuf); + goto out; + } + GST_DEBUG_OBJECT (filter, "Pushing final buffer of length: %d", len); + gst_buffer_unmap (outbuf, &outmap); + gst_buffer_set_size (outbuf, len); + if (gst_pad_push (base->srcpad, outbuf) != GST_FLOW_OK) { + GST_DEBUG_OBJECT (filter, "Failed to push the final buffer"); + goto push_fail; + } + } else { + GST_DEBUG_OBJECT (filter, + "Not pushing final buffer as we didn't have any input"); + } + } + +out: + g_mutex_unlock (&filter->decoder_lock); + + return GST_BASE_TRANSFORM_CLASS (gst_aes_dec_parent_class)->sink_event (base, + event); + + /* ERROR */ +buffer_fail: + GST_ELEMENT_ERROR (filter, RESOURCE, FAILED, (NULL), + ("Failed to allocate or map buffer for writing")); + g_mutex_unlock (&filter->decoder_lock); + + return FALSE; +cipher_fail: + GST_ELEMENT_ERROR (filter, STREAM, FAILED, ("Cipher finalization failed."), + ("Error while finalizing the stream")); + g_mutex_unlock (&filter->decoder_lock); + + return FALSE; +push_fail: + GST_ELEMENT_ERROR (filter, CORE, PAD, (NULL), + ("Failed to push the final buffer")); + g_mutex_unlock (&filter->decoder_lock); + + return FALSE; +} + +/* GstBaseTransform vmethod implementations */ +static GstFlowReturn +gst_aes_dec_transform (GstBaseTransform * base, + GstBuffer * inbuf, GstBuffer * outbuf) +{ + GstAesDec *filter = GST_AES_DEC (base); + GstFlowReturn ret = GST_FLOW_ERROR; + GstMapInfo inmap, outmap; + guchar *ciphertext; + gint ciphertext_len; + guchar *plaintext; + gint plaintext_len; + guint padding = 0; + + if (!gst_buffer_map (inbuf, &inmap, GST_MAP_READ)) { + GST_ELEMENT_ERROR (filter, RESOURCE, FAILED, (NULL), + ("Failed to map buffer for reading")); + goto cleanup; + } + if (!gst_buffer_map (outbuf, &outmap, GST_MAP_WRITE)) { + gst_buffer_unmap (inbuf, &inmap); + GST_ELEMENT_ERROR (filter, RESOURCE, FAILED, (NULL), + ("Failed to map buffer for writing")); + goto cleanup; + } + /* DECRYPTING */ + ciphertext = inmap.data; + ciphertext_len = gst_buffer_get_size (inbuf); + if (filter->awaiting_first_buffer) { + if (filter->serialize_iv) { + gchar iv_string[2 * GST_AES_BLOCK_SIZE + 1]; + + if (ciphertext_len < GST_AES_BLOCK_SIZE) { + GST_ELEMENT_ERROR (filter, RESOURCE, FAILED, (NULL), + ("Cipher text too short")); + goto cleanup; + } + memcpy (filter->iv, ciphertext, GST_AES_BLOCK_SIZE); + GST_DEBUG_OBJECT (filter, "read serialized iv: %s", + gst_aes_bytearray2hexstring (filter->iv, iv_string, + GST_AES_BLOCK_SIZE)); + ciphertext += GST_AES_BLOCK_SIZE; + ciphertext_len -= GST_AES_BLOCK_SIZE; + } + if (!gst_aes_dec_init_cipher (filter)) { + GST_ELEMENT_ERROR (filter, RESOURCE, FAILED, (NULL), + ("Failed to initialize cipher")); + goto cleanup; + } + } + plaintext = outmap.data; + + if (!EVP_CipherUpdate (filter->evp_ctx, plaintext, + &plaintext_len, ciphertext, ciphertext_len)) { + GST_ELEMENT_ERROR (filter, STREAM, FAILED, ("Cipher update failed."), + ("Error while updating openssl cipher")); + goto cleanup; + } else { + if (filter->per_buffer_padding) { + gint k; + + /* sanity check on padding value */ + padding = plaintext[plaintext_len - 1]; + if (padding == 0 || padding > GST_AES_BLOCK_SIZE) { + GST_ELEMENT_ERROR (filter, STREAM, FAILED, ("Corrupt cipher text."), + ("Illegal PKCS7 padding value %d", padding)); + goto cleanup; + } + for (k = 1; k < padding; ++k) { + if (plaintext[plaintext_len - 1 - k] != padding) { + GST_ELEMENT_ERROR (filter, STREAM, FAILED, ("Corrupt cipher text."), + ("PKCS7 padding values must all be equal")); + goto cleanup; + } + } + /* remove padding (final block padding) */ + plaintext_len -= padding; + } + if (plaintext_len > 2 * GST_AES_BLOCK_SIZE) + GST_MEMDUMP ("First 32 bytes of plain text", plaintext, + 2 * GST_AES_BLOCK_SIZE); + } + gst_buffer_unmap (inbuf, &inmap); + gst_buffer_unmap (outbuf, &outmap); + + GST_LOG_OBJECT (filter, + "Ciphertext len: %d, Plaintext len: %d, Padding: %d", + ciphertext_len, plaintext_len, padding); + gst_buffer_set_size (outbuf, plaintext_len); + ret = GST_FLOW_OK; + +cleanup: + filter->awaiting_first_buffer = FALSE; + + return ret; +} + +static GstFlowReturn +gst_aes_dec_prepare_output_buffer (GstBaseTransform * base, + GstBuffer * inbuf, GstBuffer ** outbuf) +{ + GstAesDec *filter = GST_AES_DEC (base); + GstBaseTransformClass *bclass = GST_BASE_TRANSFORM_GET_CLASS (base); + guint out_size; + + g_mutex_lock (&filter->decoder_lock); + filter->locked_properties = TRUE; + /* we need extra space at end of output buffer + * when we let OpenSSL handle PKCS7 padding */ + out_size = (gint) gst_buffer_get_size (inbuf) + + (!filter->per_buffer_padding ? GST_AES_BLOCK_SIZE : 0); + + /* Since serialized IV is stripped from first buffer, + * reduce output buffer size by GST_AES_BLOCK_SIZE in this case */ + if (filter->serialize_iv && filter->awaiting_first_buffer) { + g_assert (gst_buffer_get_size (inbuf) > GST_AES_BLOCK_SIZE); + out_size -= GST_AES_BLOCK_SIZE; + } + g_mutex_unlock (&filter->decoder_lock); + + *outbuf = gst_buffer_new_allocate (NULL, out_size, NULL); + GST_LOG_OBJECT (filter, + "Input buffer size %d,\nAllocating output buffer size: %d", + (gint) gst_buffer_get_size (inbuf), out_size); + bclass->copy_metadata (base, inbuf, *outbuf); + + return GST_FLOW_OK; +} + +static gboolean +gst_aes_dec_start (GstBaseTransform * base) +{ + GstAesDec *filter = GST_AES_DEC (base); + + GST_INFO_OBJECT (filter, "Starting"); + if (!gst_aes_dec_openssl_init (filter)) { + GST_ERROR_OBJECT (filter, "OpenSSL initialization failed"); + return FALSE; + } + + if (!filter->serialize_iv) { + if (!gst_aes_dec_init_cipher (filter)) + return FALSE; + } + GST_INFO_OBJECT (filter, "Start successful"); + + return TRUE; +} + +static gboolean +gst_aes_dec_init_cipher (GstAesDec * filter) +{ + if (!EVP_CipherInit_ex (filter->evp_ctx, filter->evp_cipher, NULL, + filter->key, filter->iv, FALSE)) { + GST_ERROR_OBJECT (filter, "Could not initialize openssl cipher"); + return FALSE; + } + if (filter->per_buffer_padding) { + if (!EVP_CIPHER_CTX_set_padding (filter->evp_ctx, 0)) { + GST_ERROR_OBJECT (filter, "Could not set padding"); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +gst_aes_dec_stop (GstBaseTransform * base) +{ + GstAesDec *filter = GST_AES_DEC (base); + + GST_INFO_OBJECT (filter, "Stopping"); + EVP_CIPHER_CTX_free (filter->evp_ctx); + + return TRUE; +} + +/* AesDec helper functions */ +static gboolean +gst_aes_dec_openssl_init (GstAesDec * filter) +{ + GST_DEBUG_OBJECT (filter, "Initializing with %s", + OpenSSL_version (OPENSSL_VERSION)); + + filter->evp_cipher = + EVP_get_cipherbyname (gst_aes_cipher_enum_to_string (filter->cipher)); + if (!filter->evp_cipher) { + GST_ERROR_OBJECT (filter, "Could not get cipher by name from openssl"); + return FALSE; + } + if (!(filter->evp_ctx = EVP_CIPHER_CTX_new ())) + return FALSE; + GST_LOG_OBJECT (filter, "Initialization successful"); + + return TRUE; +} diff --git a/ext/aes/gstaesdec.h b/ext/aes/gstaesdec.h new file mode 100644 index 0000000000..9b60c46bc8 --- /dev/null +++ b/ext/aes/gstaesdec.h @@ -0,0 +1,90 @@ +/* + * GStreamer gstreamer-aesdec + * + * Copyright, LCC (C) 2015 RidgeRun, LCC + * Copyright, 2020 Nice, Contact: Rabindra Harlalka + * + * 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1335, USA. + */ + +#ifndef __GST_AES_DEC_H__ +#define __GST_AES_DEC_H__ + +#include "gstaeshelper.h" +#include + +G_BEGIN_DECLS + +#define GST_TYPE_AES_DEC (gst_aes_dec_get_type()) +G_DECLARE_FINAL_TYPE (GstAesDec, gst_aes_dec, GST, AES_DEC, GstBaseTransform) +#define GST_AES_DEC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AES_DEC,GstAesDec)) +#define GST_AES_DEC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_AES_DEC,GstAesDecClass)) +#define GST_AES_DEC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_AES_DEC,GstAesDecClass)) +#define GST_IS_AES_DEC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AES_DEC)) +#define GST_IS_AES_DEC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_AES_DEC)) + +struct _GstAesDec +{ + GstBaseTransform element; + + /* Properties */ + GstAesCipher cipher; + guchar key[EVP_MAX_KEY_LENGTH]; + guchar iv[GST_AES_BLOCK_SIZE]; + gboolean serialize_iv; + gboolean per_buffer_padding; + + /* Element variables */ + const EVP_CIPHER *evp_cipher; + EVP_CIPHER_CTX *evp_ctx; + gboolean awaiting_first_buffer; + GMutex decoder_lock; + /* if TRUE, then properties cannot be changed */ + gboolean locked_properties; +}; + +struct _GstAesDecClass +{ + GstBaseTransformClass parent_class; +}; + +GST_ELEMENT_REGISTER_DECLARE (aesdec) + +G_END_DECLS +#endif /* __GST_AES_DEC_H__ */ diff --git a/ext/aes/gstaesenc.c b/ext/aes/gstaesenc.c new file mode 100644 index 0000000000..1119e2c826 --- /dev/null +++ b/ext/aes/gstaesenc.c @@ -0,0 +1,594 @@ +/* + * GStreamer gstreamer-aesenc + * + * Copyright, LCC (C) 2015 RidgeRun, LCC + * Copyright, LCC (C) 2016 RidgeRun, LCC + * Copyright (C) 2020 Nice, Contact: Rabindra Harlalka + * + * 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1335, USA. + */ + +/** + * SECTION:element-aesenc + * + * AES encryption + * + * ## Example + * + * |[ + * echo "This is an AES crypto test ... " > plain.txt && \ + * gst-launch-1.0 filesrc location=plain.txt ! \ + * aesenc key=1f9423681beb9a79215820f6bda73d0f iv=e9aa8e834d8d70b7e0d254ff670dd718 ! \ + * aesdec key=1f9423681beb9a79215820f6bda73d0f iv=e9aa8e834d8d70b7e0d254ff670dd718 ! \ + * filesink location=dec.txt && \ + * cat dec.txt + * + * ]| + * + * Since: 1.20 + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include "gstaeshelper.h" +#include "gstaesenc.h" + +GST_DEBUG_CATEGORY_STATIC (gst_aes_enc_debug); +#define GST_CAT_DEFAULT gst_aes_enc_debug +G_DEFINE_TYPE_WITH_CODE (GstAesEnc, gst_aes_enc, GST_TYPE_BASE_TRANSFORM, + GST_DEBUG_CATEGORY_INIT (gst_aes_enc_debug, "aesenc", 0, + "aesenc AES encryption element") + ); +GST_ELEMENT_REGISTER_DEFINE (aesenc, "aesenc", GST_RANK_PRIMARY, + GST_TYPE_AES_ENC); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("ANY") + ); + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("ANY") + ); + +static void gst_aes_enc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_aes_enc_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static GstFlowReturn gst_aes_enc_transform (GstBaseTransform * base, + GstBuffer * inbuf, GstBuffer * outbuf); +static GstFlowReturn gst_aes_enc_prepare_output_buffer (GstBaseTransform * base, + GstBuffer * inbuf, GstBuffer ** outbuf); + +static gboolean gst_aes_enc_start (GstBaseTransform * base); +static gboolean gst_aes_enc_stop (GstBaseTransform * base); +static gboolean +gst_aes_enc_sink_event (GstBaseTransform * base, GstEvent * event); + +/* aes_enc helper functions */ +static gboolean gst_aes_enc_openssl_init (GstAesEnc * filter); +static void gst_aes_enc_finalize (GObject * object); + +/* GObject vmethod implementations */ + +/* initialize class */ +static void +gst_aes_enc_class_init (GstAesEncClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->set_property = gst_aes_enc_set_property; + gobject_class->get_property = gst_aes_enc_get_property; + gobject_class->finalize = gst_aes_enc_finalize; + + gst_type_mark_as_plugin_api (GST_TYPE_AES_CIPHER, 0); + + /** + * GstAesEnc:cipher + * + * AES cipher mode (key length and mode) + * Currently, 128 and 256 bit keys are supported, + * in "cipher block chaining" (CBC) mode + * + * Since: 1.20 + */ + g_object_class_install_property (gobject_class, PROP_CIPHER, + g_param_spec_enum ("cipher", + "Cipher", + "cipher mode", + GST_TYPE_AES_CIPHER, GST_AES_DEFAULT_CIPHER_MODE, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + GST_PARAM_MUTABLE_READY))); + + /** + * GstAesEnc:serialize-iv + * + * If true, store initialization vector in first 16 bytes of first buffer + * + * Since: 1.20 + */ + g_object_class_install_property (gobject_class, PROP_SERIALIZE_IV, + g_param_spec_boolean ("serialize-iv", "Serialize IV", + "Store initialization vector in first 16 bytes of first buffer", + GST_AES_DEFAULT_SERIALIZE_IV, + G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY)); + + /** + * GstAesEnc:per-buffer-padding + * + * If true, each buffer will be padded using PKCS7 padding + * If false, only the final buffer in the stream will be padded + * (by OpenSSL) using PKCS7 + * + * Since: 1.20 + */ + g_object_class_install_property (gobject_class, PROP_PER_BUFFER_PADDING, + g_param_spec_boolean ("per-buffer-padding", "Per buffer padding", + "If true, pad each buffer using PKCS7 padding scheme. Otherwise, only" + "pad final buffer", + GST_AES_PER_BUFFER_PADDING_DEFAULT, + G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY)); + + /** + * GstAesEnc:key + * + * AES encryption key (hexadecimal) + * + * Since: 1.20 + */ + g_object_class_install_property (gobject_class, PROP_KEY, + g_param_spec_string ("key", "Key", + "AES encryption key (in hexadecimal). Length (in bytes) must be equivalent to " + "the number of bits in the key length : " + "16 bytes for AES 128 and 32 bytes for AES 256", + (gchar *) GST_AES_DEFAULT_KEY, + G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY)); + + /** + * GstAesEnc:iv + * + * AES encryption initialization vector (hexadecimal) + * + * Since: 1.20 + */ + g_object_class_install_property (gobject_class, PROP_IV, + g_param_spec_string ("iv", "Iv", + "AES encryption initialization vector (in hexadecimal). " + "Length must equal AES block length (16 bytes)", + (gchar *) GST_AES_DEFAULT_IV, + G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY)); + + gst_element_class_set_details_simple (gstelement_class, + "aesenc", + "Generic/Filter", + "AES buffer encryption", + "Rabindra Harlalka "); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&src_template)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&sink_template)); + + GST_BASE_TRANSFORM_CLASS (klass)->transform = + GST_DEBUG_FUNCPTR (gst_aes_enc_transform); + GST_BASE_TRANSFORM_CLASS (klass)->prepare_output_buffer = + GST_DEBUG_FUNCPTR (gst_aes_enc_prepare_output_buffer); + GST_BASE_TRANSFORM_CLASS (klass)->start = + GST_DEBUG_FUNCPTR (gst_aes_enc_start); + GST_BASE_TRANSFORM_CLASS (klass)->sink_event = + GST_DEBUG_FUNCPTR (gst_aes_enc_sink_event); + GST_BASE_TRANSFORM_CLASS (klass)->stop = GST_DEBUG_FUNCPTR (gst_aes_enc_stop); +} + +/* Initialize element + */ +static void +gst_aes_enc_init (GstAesEnc * filter) +{ + GST_INFO_OBJECT (filter, "Initializing plugin"); + filter->cipher = GST_AES_DEFAULT_CIPHER_MODE; + filter->awaiting_first_buffer = TRUE; + filter->per_buffer_padding = GST_AES_PER_BUFFER_PADDING_DEFAULT; + g_mutex_init (&filter->encoder_lock); +} + +static void +gst_aes_enc_finalize (GObject * object) +{ + GstAesEnc *filter = GST_AES_ENC (object); + + g_mutex_clear (&filter->encoder_lock); + G_OBJECT_CLASS (gst_aes_enc_parent_class)->finalize (object); +} + +static void +gst_aes_enc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstAesEnc *filter = GST_AES_ENC (object); + + g_mutex_lock (&filter->encoder_lock); + /* no property may be set after first output buffer is prepared */ + if (filter->locked_properties) { + GST_WARNING_OBJECT (filter, + "Properties cannot be set once buffers begin flowing in element. Ignored"); + goto cleanup; + } + switch (prop_id) { + case PROP_CIPHER: + filter->cipher = g_value_get_enum (value); + filter->evp_cipher = + EVP_get_cipherbyname (gst_aes_cipher_enum_to_string (filter->cipher)); + GST_DEBUG_OBJECT (filter, "cipher: %s", + gst_aes_cipher_enum_to_string (filter->cipher)); + break; + case PROP_SERIALIZE_IV: + filter->serialize_iv = g_value_get_boolean (value); + GST_DEBUG_OBJECT (filter, "serialize iv: %s", + filter->serialize_iv ? "TRUE" : "FALSE"); + break; + case PROP_PER_BUFFER_PADDING: + filter->per_buffer_padding = g_value_get_boolean (value); + GST_DEBUG_OBJECT (filter, "Per buffer padding: %s", + filter->per_buffer_padding ? "TRUE" : "FALSE"); + break; + case PROP_KEY: + { + guint hex_len = gst_aes_hexstring2bytearray (GST_ELEMENT (filter), + g_value_get_string (value), filter->key); + + if (!hex_len) { + GST_ERROR_OBJECT (filter, "invalid key"); + goto cleanup; + } + GST_DEBUG_OBJECT (filter, "key: %s", g_value_get_string (value)); + } + break; + case PROP_IV: + { + gchar iv_string[2 * GST_AES_BLOCK_SIZE + 1]; + guint hex_len = gst_aes_hexstring2bytearray (GST_ELEMENT (filter), + g_value_get_string (value), filter->iv); + + if (hex_len != GST_AES_BLOCK_SIZE) { + GST_ERROR_OBJECT (filter, "invalid initialization vector"); + goto cleanup; + } + GST_DEBUG_OBJECT (filter, "iv: %s", + gst_aes_bytearray2hexstring (filter->iv, iv_string, + GST_AES_BLOCK_SIZE)); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + +cleanup: + g_mutex_unlock (&filter->encoder_lock); +} + +static void +gst_aes_enc_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstAesEnc *filter = GST_AES_ENC (object); + + switch (prop_id) { + case PROP_CIPHER: + g_value_set_enum (value, filter->cipher); + break; + case PROP_SERIALIZE_IV: + g_value_set_boolean (value, filter->serialize_iv); + break; + case PROP_PER_BUFFER_PADDING: + g_value_set_boolean (value, filter->per_buffer_padding); + break; + case PROP_KEY: + g_value_set_string (value, (gchar *) filter->key); + break; + case PROP_IV: + g_value_set_string (value, (gchar *) filter->iv); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gst_aes_enc_sink_event (GstBaseTransform * base, GstEvent * event) +{ + GstAesEnc *filter = GST_AES_ENC (base); + g_mutex_lock (&filter->encoder_lock); + + if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) { + GST_DEBUG_OBJECT (filter, "Received EOS on sink pad"); + if (!filter->per_buffer_padding && !filter->awaiting_first_buffer) { + gint len; + GstBuffer *outbuf; + GstMapInfo outmap; + + outbuf = gst_buffer_new_allocate (NULL, EVP_MAX_BLOCK_LENGTH, NULL); + if (outbuf == NULL) { + GST_DEBUG_OBJECT (filter, + "Failed to allocate a new buffer of length %d", + EVP_MAX_BLOCK_LENGTH); + goto buffer_fail; + } + if (!gst_buffer_map (outbuf, &outmap, GST_MAP_WRITE)) { + GST_DEBUG_OBJECT (filter, + "gst_buffer_map on outbuf failed for final buffer."); + gst_buffer_unref (outbuf); + goto buffer_fail; + } + if (1 != EVP_CipherFinal_ex (filter->evp_ctx, outmap.data, &len)) { + GST_DEBUG_OBJECT (filter, "Could not finalize openssl encryption"); + gst_buffer_unmap (outbuf, &outmap); + gst_buffer_unref (outbuf); + goto cipher_fail; + } + if (len == 0) { + GST_DEBUG_OBJECT (filter, "Not pushing final buffer as length is 0"); + gst_buffer_unmap (outbuf, &outmap); + gst_buffer_unref (outbuf); + goto out; + } + GST_DEBUG_OBJECT (filter, "Pushing final buffer of length: %d", len); + gst_buffer_unmap (outbuf, &outmap); + gst_buffer_set_size (outbuf, len); + if (gst_pad_push (base->srcpad, outbuf) != GST_FLOW_OK) { + GST_DEBUG_OBJECT (filter, "Failed to push the final buffer"); + goto push_fail; + } + } else { + GST_DEBUG_OBJECT (filter, + "Not pushing final buffer as we didn't have any input"); + } + } + +out: + g_mutex_unlock (&filter->encoder_lock); + + return GST_BASE_TRANSFORM_CLASS (gst_aes_enc_parent_class)->sink_event (base, + event); + + /* ERROR */ +buffer_fail: + GST_ELEMENT_ERROR (filter, RESOURCE, FAILED, (NULL), + ("Failed to allocate or map buffer for writing")); + g_mutex_unlock (&filter->encoder_lock); + + return FALSE; +cipher_fail: + GST_ELEMENT_ERROR (filter, STREAM, FAILED, ("Cipher finalization failed."), + ("Error while finalizing the stream")); + g_mutex_unlock (&filter->encoder_lock); + + return FALSE; +push_fail: + GST_ELEMENT_ERROR (filter, CORE, PAD, (NULL), + ("Failed to push the final buffer")); + g_mutex_unlock (&filter->encoder_lock); + + return FALSE; +} + +/* GstBaseTransform vmethod implementations */ +static GstFlowReturn +gst_aes_enc_transform (GstBaseTransform * base, + GstBuffer * inbuf, GstBuffer * outbuf) +{ + GstAesEnc *filter = GST_AES_ENC (base); + GstFlowReturn ret = GST_FLOW_ERROR; + GstMapInfo inmap, outmap; + guchar *plaintext; + gint plaintext_len; + guchar *ciphertext; + gint ciphertext_len; + gint out_len; + + if (!gst_buffer_map (inbuf, &inmap, GST_MAP_READ)) { + GST_ELEMENT_ERROR (filter, RESOURCE, FAILED, (NULL), + ("Failed to map buffer for reading")); + goto cleanup; + } + if (!gst_buffer_map (outbuf, &outmap, GST_MAP_WRITE)) { + gst_buffer_unmap (inbuf, &inmap); + GST_ELEMENT_ERROR (filter, RESOURCE, FAILED, (NULL), + ("Failed to map buffer for writing")); + goto cleanup; + } + + /* ENCRYPTING */ + plaintext = inmap.data; + plaintext_len = inmap.size; + if (filter->padding) + plaintext_len += filter->padding - GST_AES_BLOCK_SIZE; + ciphertext = outmap.data; + if (filter->awaiting_first_buffer) { + if (!EVP_CipherInit_ex (filter->evp_ctx, filter->evp_cipher, NULL, + filter->key, filter->iv, TRUE)) { + GST_ERROR_OBJECT (filter, "Could not initialize openssl cipher"); + goto cleanup; + } + if (filter->serialize_iv) { + memcpy (ciphertext, filter->iv, GST_AES_BLOCK_SIZE); + ciphertext += GST_AES_BLOCK_SIZE; + } + } + + /* encrypt unpadded buffer */ + if (!EVP_CipherUpdate (filter->evp_ctx, ciphertext, + &ciphertext_len, plaintext, plaintext_len)) { + GST_ELEMENT_ERROR (filter, STREAM, FAILED, ("Cipher update failed."), + ("Error while updating openssl cipher")); + goto cleanup; + } else if (filter->padding) { + gint temp; + guint k; + + /* PKCS7 padding */ + memset (filter->padded_block, filter->padding, GST_AES_BLOCK_SIZE); + for (k = 0; k < GST_AES_BLOCK_SIZE - filter->padding; ++k) + filter->padded_block[k] = plaintext[plaintext_len + k]; + + /* encrypt padding buffer */ + if (!EVP_CipherUpdate (filter->evp_ctx, + ciphertext + ciphertext_len, &temp, + filter->padded_block, GST_AES_BLOCK_SIZE)) { + GST_ELEMENT_ERROR (filter, STREAM, FAILED, ("Cipher update failed."), + ("Error while updating openssl cipher")); + goto cleanup; + } else { + g_assert (temp == GST_AES_BLOCK_SIZE); + ciphertext_len += GST_AES_BLOCK_SIZE; + plaintext_len += GST_AES_BLOCK_SIZE; + } + } + gst_buffer_unmap (inbuf, &inmap); + gst_buffer_unmap (outbuf, &outmap); + + out_len = ciphertext_len + (filter->serialize_iv ? GST_AES_BLOCK_SIZE : 0); + gst_buffer_set_size (outbuf, out_len); + GST_LOG_OBJECT (filter, + "plaintext len: %d, ciphertext len: %d, padding: %d, output buffer length: %d", + plaintext_len, ciphertext_len, filter->padding, out_len); + ret = GST_FLOW_OK; + +cleanup: + filter->awaiting_first_buffer = FALSE; + + return ret; +} + +static GstFlowReturn +gst_aes_enc_prepare_output_buffer (GstBaseTransform * base, + GstBuffer * inbuf, GstBuffer ** outbuf) +{ + GstAesEnc *filter = GST_AES_ENC (base); + GstBaseTransformClass *bclass = GST_BASE_TRANSFORM_GET_CLASS (base); + guint out_size = (guint) gst_buffer_get_size (inbuf); + + g_mutex_lock (&filter->encoder_lock); + filter->locked_properties = TRUE; + if (filter->per_buffer_padding) { + /* pad to multiple of GST_AES_BLOCK_SIZE */ + filter->padding = + GST_AES_BLOCK_SIZE - (out_size & (GST_AES_BLOCK_SIZE - 1)); + out_size += filter->padding; + } else { + /* we need extra space at end of output buffer + * when we let OpenSSL handle PKCS7 padding */ + out_size += GST_AES_BLOCK_SIZE; + } + + /* add room for serialized IV at beginning of first output buffer */ + if (filter->serialize_iv && filter->awaiting_first_buffer) + out_size += GST_AES_BLOCK_SIZE; + g_mutex_unlock (&filter->encoder_lock); + + GST_LOG_OBJECT (filter, + "Input buffer size %d, output buffer size: %d. padding : %d", + (guint) gst_buffer_get_size (inbuf), out_size, filter->padding); + *outbuf = gst_buffer_new_allocate (NULL, out_size, NULL); + bclass->copy_metadata (base, inbuf, *outbuf); + + return GST_FLOW_OK; +} + +static gboolean +gst_aes_enc_start (GstBaseTransform * base) +{ + GstAesEnc *filter = GST_AES_ENC (base); + + GST_INFO_OBJECT (filter, "Starting"); + if (!gst_aes_enc_openssl_init (filter)) { + GST_ERROR_OBJECT (filter, "OpenSSL initialization failed"); + return FALSE; + } + + GST_INFO_OBJECT (filter, "Start successful"); + + return TRUE; +} + +static gboolean +gst_aes_enc_stop (GstBaseTransform * base) +{ + GstAesEnc *filter = GST_AES_ENC (base); + + GST_INFO_OBJECT (filter, "Stopping"); + EVP_CIPHER_CTX_free (filter->evp_ctx); + + return TRUE; +} + +/* AesEnc helper functions */ +static gboolean +gst_aes_enc_openssl_init (GstAesEnc * filter) +{ + GST_DEBUG_OBJECT (filter, "Initializing with %s", + OpenSSL_version (OPENSSL_VERSION)); + + filter->evp_cipher = + EVP_get_cipherbyname (gst_aes_cipher_enum_to_string (filter->cipher)); + if (!filter->evp_cipher) { + GST_ERROR_OBJECT (filter, "Could not get cipher by name from openssl"); + return FALSE; + } + if (!(filter->evp_ctx = EVP_CIPHER_CTX_new ())) + return FALSE; + GST_LOG_OBJECT (filter, "Initialization successful"); + + return TRUE; +} diff --git a/ext/aes/gstaesenc.h b/ext/aes/gstaesenc.h new file mode 100644 index 0000000000..d5acd3f299 --- /dev/null +++ b/ext/aes/gstaesenc.h @@ -0,0 +1,92 @@ +/* + * GStreamer gstreamer-aesenc + * + * Copyright, LCC (C) 2015 RidgeRun, LCC + * Copyright, 2020 Nice, Contact: Rabindra Harlalka + * + * 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1335, USA. + */ + +#ifndef __GST_AES_ENC_H__ +#define __GST_AES_ENC_H__ + +#include "gstaeshelper.h" +#include + +G_BEGIN_DECLS + +#define GST_TYPE_AES_ENC (gst_aes_enc_get_type()) +G_DECLARE_FINAL_TYPE (GstAesEnc, gst_aes_enc, GST, AES_ENC, GstBaseTransform) +#define GST_AES_ENC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AES_ENC,GstAesEnc)) +#define GST_AES_ENC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_AES_ENC,GstAesEncClass)) +#define GST_AES_ENC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_AES_ENC,GstAesEncClass)) +#define GST_IS_AES_ENC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AES_ENC)) +#define GST_IS_AES_ENC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_AES_ENC)) + +struct _GstAesEnc +{ + GstBaseTransform element; + + /* Properties */ + GstAesCipher cipher; + guchar key[EVP_MAX_KEY_LENGTH]; + guchar iv[GST_AES_BLOCK_SIZE]; + gboolean serialize_iv; + gboolean per_buffer_padding; + + /* Element variables */ + const EVP_CIPHER *evp_cipher; + EVP_CIPHER_CTX *evp_ctx; + guchar padding; + guchar padded_block[GST_AES_BLOCK_SIZE]; + gboolean awaiting_first_buffer; + GMutex encoder_lock; + /* if TRUE, then properties cannot be changed */ + gboolean locked_properties; +}; + +struct _GstAesEncClass +{ + GstBaseTransformClass parent_class; +}; + +GST_ELEMENT_REGISTER_DECLARE (aesenc) + +G_END_DECLS +#endif /* __GST_AES_ENC_H__ */ diff --git a/ext/aes/gstaeshelper.c b/ext/aes/gstaeshelper.c new file mode 100644 index 0000000000..663593a464 --- /dev/null +++ b/ext/aes/gstaeshelper.c @@ -0,0 +1,187 @@ +/* + * GStreamer gstreamer-aeshelper + * + * Copyright, LCC (C) 2015 RidgeRun, LCC + * Copyright, LCC (C) 2016 RidgeRun, LCC + * Copyright (C) 2020 Nice, Contact: Rabindra Harlalka + * + * 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1335, USA. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "gstaeshelper.h" + +GType +gst_aes_cipher_get_type (void) +{ + static GType aes_cipher_type = 0; + + if (g_once_init_enter (&aes_cipher_type)) { + static GEnumValue aes_cipher_types[] = { + {GST_AES_CIPHER_128_CBC, "AES 128 bit cipher key using CBC method", + "aes-128-cbc"}, + {GST_AES_CIPHER_256_CBC, + "AES 256 bit cipher key using CBC method", + "aes-256-cbc"}, + {0, NULL, NULL}, + }; + + GType temp = g_enum_register_static ("GstAesCipher", + aes_cipher_types); + + g_once_init_leave (&aes_cipher_type, temp); + } + + return aes_cipher_type; +} + +const gchar * +gst_aes_cipher_enum_to_string (GstAesCipher cipher) +{ + switch (cipher) { + case GST_AES_CIPHER_128_CBC: + return "aes-128-cbc"; + break; + case GST_AES_CIPHER_256_CBC: + return "aes-256-cbc"; + break; + } + + return ""; +} + + +gchar +gst_aes_nibble_to_hex (gchar in) +{ + return in < 10 ? in + 48 : in + 55; +} + +/* + * gst_aes_bytearray2hexstring + * + * convert array of bytes to hex string + * + * @param in input byte array + * @param out allocated hex string for output + * @param len length of input byte array + * + * @return output hex string + */ +gchar * +gst_aes_bytearray2hexstring (const guchar * in, gchar * const out, + const gushort len) +{ + gushort i; + gchar high; + gchar low; + + for (i = 0; i < len; i++) { + high = (in[i] & 0xF0) >> 4; + low = in[i] & 0x0F; + out[i * 2] = gst_aes_nibble_to_hex (high); + out[i * 2 + 1] = gst_aes_nibble_to_hex (low); + } + out[len * 2] = 0; /* null terminate */ + + return out; +} + +/* + * gst_aes_hexstring2bytearray + * + * convert hex string to array of bytes + * + * @param filter calling element + * @param in input hex string + * @param allocated byte array for output + * + * @return output byte array + */ +guint +gst_aes_hexstring2bytearray (GstElement * filter, const gchar * in, + guchar * out) +{ + gchar byte_val; + guint hex_count = 0; + + GST_LOG_OBJECT (filter, "Converting hex string to number"); + + g_return_val_if_fail (in && out, 0); + + while (*in != 0) { + /* Compute fist half-byte */ + if (*in >= 'A' && *in <= 'F') { + byte_val = (*in - 55) << 4; + } else if (*in >= 'a' && *in <= 'f') { + byte_val = (*in - 87) << 4; + } else if (*in >= '0' && *in <= '9') { + byte_val = (*in - 48) << 4; + } else { + return 0; + } + in++; + if (*in == 0) { + break; + } + /* Compute second half-byte */ + if (*in >= 'A' && *in <= 'F') { + *out = (*in - 55) + byte_val; + } else if (*in >= 'a' && *in <= 'f') { + *out = (*in - 87) + byte_val; + } else if (*in >= '0' && *in <= '9') { + *out = (*in - 48) + byte_val; + } else { + return 0; + } + + GST_LOG_OBJECT (filter, "ch: %c%c, hex: 0x%x", *(in - 1), *in, *out); + in++; + out++; + if (!in || !out) + return 0; + hex_count++; + } + GST_LOG_OBJECT (filter, "Hex string conversion successful"); + + return hex_count; +} diff --git a/ext/aes/gstaeshelper.h b/ext/aes/gstaeshelper.h new file mode 100644 index 0000000000..46288f2537 --- /dev/null +++ b/ext/aes/gstaeshelper.h @@ -0,0 +1,104 @@ +/* + * GStreamer gstreamer-aeshelper + * + * Copyright, LCC (C) 2015 RidgeRun, LCC + * Copyright, 2020 Nice, Contact: Rabindra Harlalka + * + * 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1335, USA. + */ + +#ifndef __GST_AES_HELPER_H__ +#define __GST_AES_HELPER_H__ + +#include +#include +#include +#include + +/** + * GstAesCipher: + * @GST_AES_CIPHER_128_CBC: AES cipher with 128 bit key using CBC + * @GST_AES_CIPHER_256_CBC: AES cipher with 256 bit key using CBC + * + * Type of AES cipher to use + * + * Since: 1.20 + */ + +typedef enum { + GST_AES_CIPHER_128_CBC, + GST_AES_CIPHER_256_CBC +} GstAesCipher; + +#define GST_AES_DEFAULT_SERIALIZE_IV FALSE +#define GST_AES_DEFAULT_KEY "" +#define GST_AES_DEFAULT_IV "" +#define GST_AES_DEFAULT_CIPHER_MODE GST_AES_CIPHER_128_CBC +#define GST_AES_PER_BUFFER_PADDING_DEFAULT TRUE +#define GST_AES_BLOCK_SIZE 16 +/* only 128 or 256 bit key length is supported */ +#define GST_AES_MAX_KEY_SIZE 32 + +enum +{ + PROP_0, + PROP_CIPHER, + PROP_SERIALIZE_IV, + PROP_KEY, + PROP_IV, + PROP_PER_BUFFER_PADDING +}; + +G_BEGIN_DECLS + +GType gst_aes_cipher_get_type (void); +#define GST_TYPE_AES_CIPHER (gst_aes_cipher_get_type ()) +const gchar* gst_aes_cipher_enum_to_string (GstAesCipher cipher); + +gchar +gst_aes_nibble_to_hex (gchar in); +gchar * +gst_aes_bytearray2hexstring (const guchar * in, gchar * const out, + const gushort len); +guint +gst_aes_hexstring2bytearray (GstElement * filter, const gchar * in, + guchar * out); + +G_END_DECLS +#endif /* __GST_AES_HELPER_H__ */ diff --git a/ext/aes/meson.build b/ext/aes/meson.build new file mode 100644 index 0000000000..c83049234a --- /dev/null +++ b/ext/aes/meson.build @@ -0,0 +1,33 @@ +aes_sources = [ + 'gstaes.c', + 'gstaeshelper.c', + 'gstaesenc.c', + 'gstaesdec.c', +] + +aes_option = get_option('aes') +if aes_option.disabled() + subdir_done() +endif + +aes_cargs = [] +aes_dep = dependency('openssl', required : get_option('aes')) +if aes_dep.found() + aes_cargs += ['-DHAVE_OPENSSL'] +else + subdir_done() +endif + +gstaes = library('gstaes', + aes_sources, + c_args : gst_plugins_bad_args + aes_cargs, + link_args : noseh_link_args, + include_directories : [configinc], + dependencies : [gstpbutils_dep, gstvideo_dep, + aes_dep, gio_dep, libm], + install : true, + install_dir : plugins_install_dir, +) +pkgconfig.generate(gstaes, install_dir : plugins_pkgconfig_install_dir) +plugins += [gstaes] +aes_dep = declare_dependency(include_directories : include_directories('.')) diff --git a/ext/meson.build b/ext/meson.build index bc19e1006c..61a394adb3 100644 --- a/ext/meson.build +++ b/ext/meson.build @@ -1,3 +1,4 @@ +subdir('aes') subdir('assrender') subdir('aom') subdir('avtp') diff --git a/meson_options.txt b/meson_options.txt index c24b3b3b06..893842d3b0 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -77,6 +77,7 @@ option('wayland', type : 'feature', value : 'auto', description : 'Wayland plugi option('x11', type : 'feature', value : 'auto', description : 'X11 support in Vulkan, GL and rfb plugins') # Feature options for plugins that need external deps +option('aes', type : 'feature', value : 'auto', description : 'AES encryption/decryption plugin') option('aom', type : 'feature', value : 'auto', description : 'AOM AV1 video codec plugin') option('avtp', type : 'feature', value : 'auto', description : 'Audio/Video Transport Protocol (AVTP) plugin') option('androidmedia', type : 'feature', value : 'auto', description : 'Video capture and codec plugins for Android') diff --git a/tests/check/elements/aesdec.c b/tests/check/elements/aesdec.c new file mode 100644 index 0000000000..6ffbc4b2bb --- /dev/null +++ b/tests/check/elements/aesdec.c @@ -0,0 +1,181 @@ +/* GStreamer + * + * Copyright (C) 2021 Aaron Boxer + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include + +unsigned char plain16[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F +}; + +unsigned char enc16[] = { + 0xfc, 0x49, 0x14, 0xc6, 0xee, 0x06, 0xe1, 0xb1, + 0xc7, 0xa2, 0x3a, 0x05, 0x13, 0x15, 0x29, 0x27, + 0x40, 0xee, 0xfd, 0xcb, 0x3b, 0xbe, 0xf3, 0x0b, + 0xa7, 0xaf, 0x5e, 0x20, 0x87, 0x78, 0x8a, 0x45 +}; + +unsigned char enc16_serialize[] = { + 0xe9, 0xaa, 0x8e, 0x83, 0x4d, 0x8d, 0x70, 0xb7, + 0xe0, 0xd2, 0x54, 0xff, 0x67, 0x0d, 0xd7, 0x18, + 0xfc, 0x49, 0x14, 0xc6, 0xee, 0x06, 0xe1, 0xb1, + 0xc7, 0xa2, 0x3a, 0x05, 0x13, 0x15, 0x29, 0x27, + 0x40, 0xee, 0xfd, 0xcb, 0x3b, 0xbe, 0xf3, 0x0b, + 0xa7, 0xaf, 0x5e, 0x20, 0x87, 0x78, 0x8a, 0x45 +}; + +unsigned char plain17[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10 +}; + +unsigned char enc17[] = { + 0xfc, 0x49, 0x14, 0xc6, 0xee, 0x06, 0xe1, 0xb1, + 0xc7, 0xa2, 0x3a, 0x05, 0x13, 0x15, 0x29, 0x27, + 0xe1, 0xe0, 0xaa, 0xf4, 0xe8, 0x29, 0x7c, 0x9f, + 0xc4, 0xe3, 0x11, 0x4a, 0x97, 0x58, 0x9c, 0xa5 +}; + +unsigned char enc17_serialize[] = { + 0xe9, 0xaa, 0x8e, 0x83, 0x4d, 0x8d, 0x70, 0xb7, + 0xe0, 0xd2, 0x54, 0xff, 0x67, 0x0d, 0xd7, 0x18, + 0xfc, 0x49, 0x14, 0xc6, 0xee, 0x06, 0xe1, 0xb1, + 0xc7, 0xa2, 0x3a, 0x05, 0x13, 0x15, 0x29, 0x27, + 0xe1, 0xe0, 0xaa, 0xf4, 0xe8, 0x29, 0x7c, 0x9f, + 0xc4, 0xe3, 0x11, 0x4a, 0x97, 0x58, 0x9c, 0xa5 +}; + +static void +run (gboolean per_buffer_padding, + gboolean serialize_iv, + guchar * in_ref, gsize in_ref_len, guchar * out_ref, gsize out_ref_len) +{ + + GstHarness *h; + GstBuffer *buf, *outbuf; + gint out_ref_len_truncated = out_ref_len & ~0xF; + + h = gst_harness_new ("aesdec"); + gst_harness_set_src_caps_str (h, "video/x-raw"); + + g_object_set (h->element, + "key", "1f9423681beb9a79215820f6bda73d0f", + "iv", "e9aa8e834d8d70b7e0d254ff670dd718", + "per-buffer-padding", per_buffer_padding, + "serialize-iv", serialize_iv, NULL); + + buf = gst_buffer_new_and_alloc (in_ref_len); + gst_buffer_fill (buf, 0, in_ref, in_ref_len); + outbuf = gst_harness_push_and_pull (h, gst_buffer_ref (buf)); + + fail_unless (gst_buffer_memcmp (outbuf, 0, out_ref, + out_ref_len_truncated) == 0); + + gst_buffer_unref (outbuf); + gst_buffer_unref (buf); + + if (!per_buffer_padding) { + gint final_bytes = out_ref_len & 0xF; + + if (final_bytes != 0) { + gst_harness_push_event (h, gst_event_new_eos ()); + outbuf = gst_harness_try_pull (h); + fail_unless (outbuf); + fail_unless (gst_buffer_get_size (outbuf) == final_bytes); + fail_unless (gst_buffer_memcmp (outbuf, 0, + out_ref + out_ref_len_truncated, final_bytes) == 0); + gst_buffer_unref (outbuf); + } + } + + gst_harness_teardown (h); +} + +GST_START_TEST (text16) +{ + run (TRUE, FALSE, enc16, sizeof (enc16), plain16, sizeof (plain16)); +} + +GST_END_TEST; + +GST_START_TEST (text16_serialize) +{ + run (TRUE, TRUE, enc16_serialize, sizeof (enc16_serialize), plain16, + sizeof (plain16)); +} + +GST_END_TEST; + +GST_START_TEST (text16_serialize_no_per_buffer_padding) +{ + run (FALSE, TRUE, enc16_serialize, sizeof (enc16_serialize), plain16, + sizeof (plain16)); +} + +GST_END_TEST; + + +GST_START_TEST (text17) +{ + run (TRUE, FALSE, enc17, sizeof (enc17), plain17, sizeof (plain17)); +} + +GST_END_TEST; + +GST_START_TEST (text17_serialize) +{ + run (TRUE, TRUE, enc17_serialize, sizeof (enc17_serialize), plain17, + sizeof (plain17)); +} + +GST_END_TEST; + + +GST_START_TEST (text17_serialize_no_per_buffer_padding) +{ + run (FALSE, TRUE, enc17_serialize, sizeof (enc17_serialize), plain17, + sizeof (plain17)); +} + +GST_END_TEST; + +static Suite * +aesdec_suite (void) +{ + Suite *s = suite_create ("aesdec"); + TCase *tc = tcase_create ("general"); + + suite_add_tcase (s, tc); + tcase_add_test (tc, text16); + tcase_add_test (tc, text16_serialize); + tcase_add_test (tc, text16_serialize_no_per_buffer_padding); + tcase_add_test (tc, text17); + tcase_add_test (tc, text17_serialize); + tcase_add_test (tc, text17_serialize_no_per_buffer_padding); + return s; +} + +GST_CHECK_MAIN (aesdec); diff --git a/tests/check/elements/aesenc.c b/tests/check/elements/aesenc.c new file mode 100644 index 0000000000..5662e678ed --- /dev/null +++ b/tests/check/elements/aesenc.c @@ -0,0 +1,179 @@ +/* GStreamer + * + * Copyright (C) 2021 Aaron Boxer + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include + +unsigned char plain16[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F +}; + +unsigned char enc16[] = { + 0xfc, 0x49, 0x14, 0xc6, 0xee, 0x06, 0xe1, 0xb1, + 0xc7, 0xa2, 0x3a, 0x05, 0x13, 0x15, 0x29, 0x27, + 0x40, 0xee, 0xfd, 0xcb, 0x3b, 0xbe, 0xf3, 0x0b, + 0xa7, 0xaf, 0x5e, 0x20, 0x87, 0x78, 0x8a, 0x45 +}; + +unsigned char enc16_serialize[] = { + 0xe9, 0xaa, 0x8e, 0x83, 0x4d, 0x8d, 0x70, 0xb7, + 0xe0, 0xd2, 0x54, 0xff, 0x67, 0x0d, 0xd7, 0x18, + 0xfc, 0x49, 0x14, 0xc6, 0xee, 0x06, 0xe1, 0xb1, + 0xc7, 0xa2, 0x3a, 0x05, 0x13, 0x15, 0x29, 0x27, + 0x40, 0xee, 0xfd, 0xcb, 0x3b, 0xbe, 0xf3, 0x0b, + 0xa7, 0xaf, 0x5e, 0x20, 0x87, 0x78, 0x8a, 0x45 +}; + +unsigned char enc16_serialize_no_per_buffer_padding[] = { + 0xe9, 0xaa, 0x8e, 0x83, 0x4d, 0x8d, 0x70, 0xb7, + 0xe0, 0xd2, 0x54, 0xff, 0x67, 0x0d, 0xd7, 0x18, + 0xfc, 0x49, 0x14, 0xc6, 0xee, 0x06, 0xe1, 0xb1, + 0xc7, 0xa2, 0x3a, 0x05, 0x13, 0x15, 0x29, 0x27 +}; + + +unsigned char plain17[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10 +}; + +unsigned char enc17[] = { + 0xfc, 0x49, 0x14, 0xc6, 0xee, 0x06, 0xe1, 0xb1, + 0xc7, 0xa2, 0x3a, 0x05, 0x13, 0x15, 0x29, 0x27, + 0xe1, 0xe0, 0xaa, 0xf4, 0xe8, 0x29, 0x7c, 0x9f, + 0xc4, 0xe3, 0x11, 0x4a, 0x97, 0x58, 0x9c, 0xa5 +}; + +unsigned char enc17_serialize[] = { + 0xe9, 0xaa, 0x8e, 0x83, 0x4d, 0x8d, 0x70, 0xb7, + 0xe0, 0xd2, 0x54, 0xff, 0x67, 0x0d, 0xd7, 0x18, + 0xfc, 0x49, 0x14, 0xc6, 0xee, 0x06, 0xe1, 0xb1, + 0xc7, 0xa2, 0x3a, 0x05, 0x13, 0x15, 0x29, 0x27, + 0xe1, 0xe0, 0xaa, 0xf4, 0xe8, 0x29, 0x7c, 0x9f, + 0xc4, 0xe3, 0x11, 0x4a, 0x97, 0x58, 0x9c, 0xa5 +}; + +unsigned char enc17_serialize_no_per_buffer_padding[] = { + 0xe9, 0xaa, 0x8e, 0x83, 0x4d, 0x8d, 0x70, 0xb7, + 0xe0, 0xd2, 0x54, 0xff, 0x67, 0x0d, 0xd7, 0x18, + 0xfc, 0x49, 0x14, 0xc6, 0xee, 0x06, 0xe1, 0xb1, + 0xc7, 0xa2, 0x3a, 0x05, 0x13, 0x15, 0x29, 0x27, +}; + +static void +run (gboolean per_buffer_padding, + gboolean serialize_iv, + guchar * in_ref, gsize in_ref_len, guchar * out_ref, gsize out_ref_len) +{ + GstHarness *h; + GstBuffer *buf, *outbuf; + + h = gst_harness_new ("aesenc"); + gst_harness_set_src_caps_str (h, "video/x-raw"); + + g_object_set (h->element, + "key", "1f9423681beb9a79215820f6bda73d0f", + "iv", "e9aa8e834d8d70b7e0d254ff670dd718", + "per-buffer-padding", per_buffer_padding, + "serialize-iv", serialize_iv, NULL); + + buf = gst_buffer_new_and_alloc (in_ref_len); + gst_buffer_fill (buf, 0, in_ref, in_ref_len); + outbuf = gst_harness_push_and_pull (h, gst_buffer_ref (buf)); + + fail_unless (gst_buffer_memcmp (outbuf, 0, out_ref, out_ref_len) == 0); + + gst_buffer_unref (outbuf); + gst_buffer_unref (buf); + + gst_harness_teardown (h); +} + +GST_START_TEST (text16) +{ + run (TRUE, FALSE, plain16, sizeof (plain16), enc16, sizeof (enc16)); +} + +GST_END_TEST; + +GST_START_TEST (text16_serialize) +{ + run (TRUE, TRUE, plain16, sizeof (plain16), enc16_serialize, + sizeof (enc16_serialize)); +} + +GST_END_TEST; + +GST_START_TEST (text16_serialize_no_per_buffer_padding) +{ + run (FALSE, TRUE, plain16, sizeof (plain16), + enc16_serialize_no_per_buffer_padding, + sizeof (enc16_serialize_no_per_buffer_padding)); +} + +GST_END_TEST; + +GST_START_TEST (text17) +{ + run (TRUE, FALSE, plain17, sizeof (plain17), enc17, sizeof (enc17)); +} + +GST_END_TEST; + +GST_START_TEST (text17_serialize) +{ + run (TRUE, TRUE, plain17, sizeof (plain17), enc17_serialize, + sizeof (enc17_serialize)); +} + +GST_END_TEST; + +GST_START_TEST (text17_serialize_no_per_buffer_padding) +{ + run (FALSE, TRUE, plain17, sizeof (plain17), + enc17_serialize_no_per_buffer_padding, + sizeof (enc17_serialize_no_per_buffer_padding)); +} + +GST_END_TEST; + +static Suite * +aesenc_suite (void) +{ + Suite *s = suite_create ("aesenc"); + TCase *tc = tcase_create ("general"); + + suite_add_tcase (s, tc); + tcase_add_test (tc, text16); + tcase_add_test (tc, text16_serialize); + tcase_add_test (tc, text16_serialize_no_per_buffer_padding); + tcase_add_test (tc, text17); + tcase_add_test (tc, text17_serialize); + tcase_add_test (tc, text17_serialize_no_per_buffer_padding); + return s; +} + +GST_CHECK_MAIN (aesenc); diff --git a/tests/check/meson.build b/tests/check/meson.build index b4055a931a..713b999338 100644 --- a/tests/check/meson.build +++ b/tests/check/meson.build @@ -18,9 +18,12 @@ nalutils_dep = gstcodecparsers_dep.partial_dependency (compile_args: true, inclu enable_gst_play_tests = get_option('gst_play_tests') libsoup_dep = dependency('libsoup-2.4', version : '>=2.48', required : enable_gst_play_tests, fallback : ['libsoup', 'libsoup_dep']) +aes_dep = dependency('openssl', required : false) # name, condition when to skip the test and extra dependencies base_tests = [ + [['elements/aesenc.c'], false, [aes_dep]], + [['elements/aesdec.c'], false, [aes_dep]], [['elements/aiffparse.c']], [['elements/asfmux.c']], [['elements/autoconvert.c']],