diff options
Diffstat (limited to 'ext/aes/gstaesenc.c')
-rw-r--r-- | ext/aes/gstaesenc.c | 594 |
1 files changed, 594 insertions, 0 deletions
diff --git a/ext/aes/gstaesenc.c b/ext/aes/gstaesenc.c new file mode 100644 index 000000000..1119e2c82 --- /dev/null +++ b/ext/aes/gstaesenc.c @@ -0,0 +1,594 @@ +/* + * GStreamer gstreamer-aesenc + * + * Copyright, LCC (C) 2015 RidgeRun, LCC <carsten.behling@ridgerun.com> + * Copyright, LCC (C) 2016 RidgeRun, LCC <jose.jimenez@ridgerun.com> + * Copyright (C) 2020 Nice, Contact: Rabindra Harlalka <Rabindra.Harlalka@nice.com> + * + * 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 <config.h> +#endif + +#include <gst/gst.h> +#include <gst/base/gstbasetransform.h> +#include <string.h> +#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 <Rabindra.Harlalka@nice.com>"); + + 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; +} |