├── NEWS ├── README ├── gst-libs ├── meson.build └── gst │ ├── meson.build │ ├── gstaesctr.h │ └── gstaesctr.c ├── AUTHORS ├── ChangeLog ├── tests ├── meson.build └── aesctr │ └── decrypt.c ├── src ├── meson.build ├── gstcencelements.c ├── gstcencdec.h └── gstcencdec.c ├── meson.build ├── store-key.py ├── README.md └── COPYING /NEWS: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | see README.md 2 | -------------------------------------------------------------------------------- /gst-libs/meson.build: -------------------------------------------------------------------------------- 1 | subdir('gst') -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Alex Ashley 2 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2013-09-03 Alex Ashley 2 | 3 | Started implementation 4 | -------------------------------------------------------------------------------- /gst-libs/gst/meson.build: -------------------------------------------------------------------------------- 1 | gst_aesctr = static_library('gstaesctr-@0@'.format(apiversion), 2 | ['gstaesctr.c'], 3 | dependencies : [gst_dep], 4 | install : false 5 | ) 6 | 7 | gst_aesctr_dep = declare_dependency(link_with : gst_aesctr, 8 | dependencies : [openssl_dep], 9 | include_directories : [include_directories('..')] 10 | ) 11 | -------------------------------------------------------------------------------- /tests/meson.build: -------------------------------------------------------------------------------- 1 | element_tests = ['aesctr/decrypt.c'] 2 | 3 | foreach test_file : element_tests 4 | test_name = test_file.split('.').get(0).underscorify() 5 | 6 | exe = executable(test_name, test_file, 7 | include_directories : [configinc], 8 | dependencies : [gst_aesctr_dep, gst_check_dep] 9 | ) 10 | 11 | test(test_name, exe, timeout: 3 * 60) 12 | endforeach -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | gst_cencdec_elements_sources = [ 2 | 'gstcencdec.c', 3 | 'gstcencelements.c' 4 | ] 5 | 6 | gst_cencdec = library('gstcencdec', 7 | gst_cencdec_elements_sources, 8 | dependencies : [gst_dep, gst_base_dep, gst_aesctr_dep, libxml2_dep], 9 | include_directories : [configinc], 10 | c_args : gst_c_args, 11 | install : true, 12 | install_dir : plugins_install_dir) 13 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('gst-cencdec', 'c', 2 | version : '0.1.0', 3 | default_options : [ 'warning_level=3', 4 | 'buildtype=debugoptimized' ]) 5 | 6 | gst_req = '>= 1.8.0' 7 | apiversion = '1.0' 8 | 9 | gst_cencdec_version = meson.project_version() 10 | 11 | glib_dep = dependency('glib-2.0') 12 | gst_dep = dependency('gstreamer-1.0', version : gst_req) 13 | gst_base_dep = dependency('gstreamer-base-1.0', version : gst_req) 14 | gst_check_dep = dependency('gstreamer-check-1.0', version : gst_req) 15 | 16 | configinc = include_directories('.') 17 | 18 | plugins_install_dir = join_paths(get_option('libdir'), 'gstreamer-1.0') 19 | 20 | core_conf = configuration_data() 21 | core_conf.set_quoted('VERSION', gst_cencdec_version) 22 | core_conf.set_quoted('PACKAGE_NAME', 'gst-cencdec') 23 | core_conf.set_quoted('PACKAGE', 'gst-cencdec') 24 | 25 | gst_c_args = ['-DHAVE_CONFIG_H'] 26 | 27 | configure_file(output : 'config.h', configuration : core_conf) 28 | 29 | libxml2_dep = dependency('libxml-2.0', required : true) 30 | openssl_dep = dependency('openssl', version: '>= 1.0.0g', required : true) 31 | 32 | subdir('gst-libs') 33 | subdir('src') 34 | subdir('tests') -------------------------------------------------------------------------------- /store-key.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import base64 4 | import binascii 5 | import hashlib 6 | import os 7 | import re 8 | import sys 9 | 10 | def store_key(filename, key): 11 | kfile = open(filename,'wb') 12 | kfile.write(key) 13 | kfile.close() 14 | print('Key stored: {0}'.format(filename)) 15 | 16 | if len(sys.argv)<3: 17 | print('Usage: %s '%(sys.argv[0])) 18 | sys.exit(1) 19 | 20 | kid_str = sys.argv[1].strip().replace('-','') 21 | bin_kid= binascii.unhexlify(kid_str) 22 | if len(bin_kid)!=16: 23 | print('ERROR: KID is not 16 bytes long') 24 | sys.exit(2) 25 | 26 | key_str = sys.argv[2].strip() 27 | if re.match(r'^[0-9a-f]+$', key_str, re.IGNORECASE): 28 | bin_key = binascii.unhexlify(key_str) 29 | else: 30 | bin_key = base64.b64decode(key_str) 31 | if len(bin_key)!=16: 32 | print('ERROR: Key is not 16 bytes long') 33 | sys.exit(2) 34 | 35 | # Marlin naming 36 | id_str = ':'.join(['urn','marlin','kid',binascii.hexlify(bin_kid)]) 37 | filename = os.path.join('/tmp', hashlib.sha1(id_str).hexdigest()) + '.key' 38 | store_key(filename, bin_key) 39 | 40 | # Clearkey naming 41 | filename = os.path.join('/tmp', binascii.hexlify(bin_kid)) + '.key' 42 | store_key(filename, bin_key) 43 | -------------------------------------------------------------------------------- /gst-libs/gst/gstaesctr.h: -------------------------------------------------------------------------------- 1 | 2 | /* GStreamer ISO MPEG-DASH common encryption decryption 3 | * Copyright (C) 2013 YouView TV Ltd. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Library General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Library General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Library General Public 16 | * License along with this library; if not, write to the 17 | * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, 18 | * Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | #ifndef _GST_AES_CTR_DECRYPT_H_ 22 | #define _GST_AES_CTR_DECRYPT_H_ 23 | 24 | #include 25 | #include 26 | 27 | G_BEGIN_DECLS 28 | 29 | typedef struct _AesCtrState AesCtrState; 30 | 31 | AesCtrState * gst_aes_ctr_decrypt_new(GBytes *key, GBytes *iv); 32 | AesCtrState * gst_aes_ctr_decrypt_ref(AesCtrState *state); 33 | void gst_aes_ctr_decrypt_unref(AesCtrState *state); 34 | 35 | void gst_aes_ctr_decrypt_ip(AesCtrState *state, 36 | unsigned char *data, 37 | int length); 38 | 39 | G_END_DECLS 40 | #endif 41 | -------------------------------------------------------------------------------- /src/gstcencelements.c: -------------------------------------------------------------------------------- 1 | /* GStreamer ISO MPEG DASH common encryption decryptor 2 | * Copyright (C) 2013 YouView TV Ltd. 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 51 Franklin Street, Suite 500, 17 | * Boston, MA 02110-1335, USA. 18 | */ 19 | 20 | 21 | #ifdef HAVE_CONFIG_H 22 | #include "config.h" 23 | #endif 24 | 25 | #include "gstcencdec.h" 26 | 27 | static gboolean 28 | plugin_init (GstPlugin * plugin) 29 | { 30 | return gst_element_register (plugin, "cencdec", GST_RANK_PRIMARY, 31 | GST_TYPE_CENC_DECRYPT); 32 | } 33 | 34 | GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, 35 | GST_VERSION_MINOR, 36 | cencdec, 37 | "ISOBMFF common encryption element", 38 | plugin_init, 39 | VERSION, 40 | "LGPL", 41 | PACKAGE_NAME, 42 | "https://github.com/asrashley/gst-cencdec.git"); 43 | -------------------------------------------------------------------------------- /src/gstcencdec.h: -------------------------------------------------------------------------------- 1 | /* GStreamer ISO MPEG-DASH common encryption decryption 2 | * Copyright (C) 2013 YouView TV Ltd. 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, 17 | * Boston, MA 02110-1301, USA. 18 | */ 19 | 20 | #ifndef _GST_CENC_DECRYPT_H_ 21 | #define _GST_CENC_DECRYPT_H_ 22 | 23 | #include 24 | 25 | G_BEGIN_DECLS 26 | #define GST_TYPE_CENC_DECRYPT (gst_cenc_decrypt_get_type()) 27 | #define GST_CENC_DECRYPT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CENC_DECRYPT,GstCencDecrypt)) 28 | #define GST_CENC_DECRYPT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CENC_DECRYPT,GstCencDecryptClass)) 29 | #define GST_IS_CENC_DECRYPT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CENC_DECRYPT)) 30 | #define GST_IS_CENC_DECRYPT_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CENC_DECRYPT)) 31 | typedef struct _GstCencDecrypt GstCencDecrypt; 32 | typedef struct _GstCencDecryptClass GstCencDecryptClass; 33 | 34 | 35 | GType gst_cenc_decrypt_get_type (void); 36 | 37 | G_END_DECLS 38 | #endif 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This package provides an example GStreamer element that implements 2 | DASH Common Encryption (ISO/IEC23001-7 Information technology — MPEG 3 | systems technologies — Part 7: Common encryption in ISO base media 4 | file format files). 5 | 6 | It takes video or audio (of type "application/x-cenc") 7 | from qtdemux and performs the AES-CTR decryption and outputs the decrypted 8 | content on a source pad. 9 | 10 | Requirements 11 | ------------ 12 | * gstreamer 1.11 13 | * Openssl >=1.0.0h 14 | 15 | Usage 16 | ----- 17 | The decryptor does not implement a real DRM system. It provides partial 18 | support for Marlin and Clearkey protection systems, in that it advertises 19 | these two systems in its supported content protection system IDs. 20 | 21 | It has no support for robustly acquiring the keys, it just looks in the 22 | local filesystem for a file that contains the key. 23 | 24 | In the case of Marlin, it performs a sha1 hash of the content ID, converts 25 | that to a hex string and then looks for a file 26 | /tmp/\.key that contains the binary data of the key. 27 | 28 | In the case of Clearkey, it converts the KID to a hex string and then looks 29 | for a file /tmp/\.key that contains the binary data of the key. 30 | 31 | There is a store-key.py Python application that will write the key into the 32 | appropriate location. The usage is: 33 | 34 | ./store-key.py 35 | 36 | Where: 37 | is the hex value of the key ID 38 | is either the hex or base64 encoded value of the key 39 | 40 | Example usage: 41 | 42 | ./store-key.py 00000000000000000000000000000000 0123456789ABCDEF0123456789ABCDEF 43 | ./store-key.py 0bbc0bbc0bbc0bbc0bbc0bbc0bbc1bbc ABCDEF0123456789ABCDEF0123456789 44 | gst-launch-1.0 playbin uri='http://test-media.youview.co.uk/ondemand/bbb/avc3/1/2drm_manifest.mpd' 45 | 46 | 47 | Clearkey example: 48 | 49 | ./store-key.py 0872786e-f9e7-465f-a3a2-4e5b0ef8fa45 'wyYRebq2Hu7JedLUBpURzw==' 50 | ./store-key.py 2d6e9387-60ca-4145-aec2-c40837b4b026 'QtC/8bYPe+SfF9YDSE0MuQ==' 51 | ./store-key.py 4222bd78-bc45-41bf-b63e-6f814dc391df 'GAMi9v92b9ca5yBwaptN+Q==' 52 | ./store-key.py 585f233f-3072-46f1-9fa4-6dc22c66a014 'jayKpC3tmPq4YKXkapa8FA==' 53 | ./store-key.py 9eb4050de44b4802932e27d75083e266 166634c675823c235a4a9446fad52e4d 54 | ./store-key.py c14f0709-f2b9-4427-916b-61b52586506a '7fsXeXJHs8enQ0SEfkhTBQ==' 55 | ./store-key.py de02f07f-a098-4ee0-b556-907c0d17fbbc 'GQnGyyKBez4x8aNTD6cNzw==' 56 | gst-launch-1.0 playbin uri='https://media.axprod.net/TestVectors/v7-MultiDRM-MultiKey-MultiPeriod/Manifest_1080p_ClearKey.mpd' 57 | 58 | or 59 | 60 | gst-launch-1.0 playbin uri='https://media.axprod.net/TestVectors/v7-MultiDRM-MultiKey/Manifest_AudioOnly_ClearKey.mpd' 61 | -------------------------------------------------------------------------------- /gst-libs/gst/gstaesctr.c: -------------------------------------------------------------------------------- 1 | /* GStreamer ISO MPEG DASH common encryption decryptor 2 | * Copyright (C) 2013 YouView TV Ltd. 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 51 Franklin Street, Suite 500, 17 | * Boston, MA 02110-1335, USA. 18 | */ 19 | 20 | #include 21 | #include 22 | 23 | #if OPENSSL_VERSION_NUMBER > 0x010100000 24 | #include 25 | #endif 26 | 27 | #include 28 | 29 | #include "gstaesctr.h" 30 | 31 | struct _AesCtrState { 32 | volatile gint refcount; 33 | AES_KEY key; 34 | unsigned char ivec[16]; 35 | unsigned int num; 36 | unsigned char ecount[16]; 37 | }; 38 | 39 | AesCtrState * 40 | gst_aes_ctr_decrypt_new(GBytes *key, GBytes *iv) 41 | { 42 | unsigned char *buf; 43 | GstMapInfo map; 44 | gsize iv_length; 45 | AesCtrState *state; 46 | 47 | g_return_val_if_fail(key!=NULL,NULL); 48 | g_return_val_if_fail(iv!=NULL,NULL); 49 | 50 | state = g_slice_new(AesCtrState); 51 | if(!state){ 52 | GST_ERROR ("Failed to allocate AesCtrState"); 53 | return NULL; 54 | } 55 | g_return_val_if_fail (g_bytes_get_size (key) == 16, NULL); 56 | AES_set_encrypt_key ((const unsigned char*) g_bytes_get_data (key, NULL), 57 | 8 * g_bytes_get_size (key), &state->key); 58 | 59 | buf = (unsigned char*)g_bytes_get_data(iv, &iv_length); 60 | g_return_val_if_fail(buf!=NULL, NULL); 61 | g_return_val_if_fail(iv_length==8 || iv_length==16, NULL); 62 | state->num = 0; 63 | memset(state->ecount, 0, 16); 64 | if(iv_length==8){ 65 | memset(state->ivec + 8, 0, 8); 66 | memcpy(state->ivec, buf, 8); 67 | } 68 | else{ 69 | memcpy(state->ivec, buf, 16); 70 | } 71 | return state; 72 | } 73 | 74 | AesCtrState* 75 | gst_aes_ctr_decrypt_ref(AesCtrState *state) 76 | { 77 | g_return_val_if_fail (state != NULL, NULL); 78 | 79 | g_atomic_int_inc (&state->refcount); 80 | 81 | return state; 82 | } 83 | 84 | void 85 | gst_aes_ctr_decrypt_unref(AesCtrState *state) 86 | { 87 | g_return_if_fail (state != NULL); 88 | 89 | if (g_atomic_int_dec_and_test (&state->refcount)) { 90 | g_slice_free (AesCtrState, state); 91 | } 92 | } 93 | 94 | 95 | void 96 | gst_aes_ctr_decrypt_ip(AesCtrState *state, 97 | unsigned char *data, 98 | int length) 99 | { 100 | #if OPENSSL_VERSION_NUMBER > 0x010100000 101 | CRYPTO_ctr128_encrypt(data, data, length, &state->key, state->ivec, 102 | state->ecount, &state->num, (block128_f)AES_encrypt); 103 | #else 104 | AES_ctr128_encrypt(data, data, length, &state->key, state->ivec, 105 | state->ecount, &state->num); 106 | #endif 107 | } 108 | 109 | G_DEFINE_BOXED_TYPE (AesCtrState, gst_aes_ctr, 110 | (GBoxedCopyFunc) gst_aes_ctr_decrypt_ref, 111 | (GBoxedFreeFunc) gst_aes_ctr_decrypt_unref); 112 | -------------------------------------------------------------------------------- /tests/aesctr/decrypt.c: -------------------------------------------------------------------------------- 1 | /* GStreamer ISO MPEG DASH common encryption decryptor 2 | * Copyright (C) 2013 YouView TV Ltd. 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 51 Franklin Street, Suite 500, 17 | * Boston, MA 02110-1335, USA. 18 | */ 19 | #include 20 | #include 21 | #include 22 | 23 | 24 | static AesCtrState * 25 | setup_aes_decrypt() 26 | { 27 | /* NIST SP800-38a section F.5.2; CTR-AES128 Decrypt */ 28 | const guint8 Key[]={ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c }; 29 | const guint8 IV[] = { 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff }; 30 | AesCtrState *state; 31 | GBytes *gkey; 32 | GBytes *giv; 33 | 34 | gkey = g_bytes_new_static(Key,sizeof(Key)); 35 | fail_if(gkey==NULL); 36 | giv = g_bytes_new_static(IV,sizeof(IV)); 37 | fail_if(giv==NULL); 38 | state = gst_aes_ctr_decrypt_new(gkey, giv); 39 | fail_if(state==NULL); 40 | g_bytes_unref(gkey); 41 | g_bytes_unref(giv); 42 | 43 | return state; 44 | } 45 | 46 | static void decrypt_block(AesCtrState *state, 47 | const guint8 *Ciphertext, 48 | const guint8 *Plaintext, 49 | guint length) 50 | { 51 | GstBuffer *buf; 52 | GstMapInfo info; 53 | gboolean rv; 54 | int i; 55 | 56 | buf = gst_buffer_new_allocate (NULL,length,NULL); 57 | fail_if(buf==NULL); 58 | gst_buffer_fill(buf,0,Ciphertext,length); 59 | rv = gst_buffer_map(buf,&info,GST_MAP_READWRITE); 60 | fail_unless(rv==TRUE); 61 | gst_aes_ctr_decrypt_ip(state, info.data, info.size); 62 | for(i=0; i 453 | Copyright (C) 454 | 455 | This library is free software; you can redistribute it and/or 456 | modify it under the terms of the GNU Library General Public 457 | License as published by the Free Software Foundation; either 458 | version 2 of the License, or (at your option) any later version. 459 | 460 | This library is distributed in the hope that it will be useful, 461 | but WITHOUT ANY WARRANTY; without even the implied warranty of 462 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 463 | Library General Public License for more details. 464 | 465 | You should have received a copy of the GNU Library General Public 466 | License along with this library; if not, write to the Free 467 | Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. 468 | 469 | Also add information on how to contact you by electronic and paper mail. 470 | 471 | You should also get your employer (if you work as a programmer) or your 472 | school, if any, to sign a "copyright disclaimer" for the library, if 473 | necessary. Here is a sample; alter the names: 474 | 475 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 476 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 477 | 478 | , 1 April 1990 479 | Ty Coon, President of Vice 480 | 481 | That's all there is to it! 482 | -------------------------------------------------------------------------------- /src/gstcencdec.c: -------------------------------------------------------------------------------- 1 | /* GStreamer ISO MPEG DASH common encryption decryptor 2 | * Copyright (C) 2013 YouView TV Ltd. 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 51 Franklin Street, Suite 500, 17 | * Boston, MA 02110-1335, USA. 18 | */ 19 | 20 | /** 21 | * SECTION:element-gstcencdecrypt 22 | * 23 | * Decrypts media that has been encrypted using the ISOBMFF Common Encryption 24 | * standard. 25 | * 26 | */ 27 | 28 | #ifdef HAVE_CONFIG_H 29 | #include "config.h" 30 | #endif 31 | 32 | #include 33 | #include 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include 43 | 44 | #include 45 | #include 46 | #include 47 | 48 | #include "gstcencdec.h" 49 | 50 | GST_DEBUG_CATEGORY_STATIC (gst_cenc_decrypt_debug_category); 51 | #define GST_CAT_DEFAULT gst_cenc_decrypt_debug_category 52 | 53 | #define KID_LENGTH 16 54 | #define KEY_LENGTH 16 55 | 56 | typedef enum 57 | { 58 | GST_DRM_MARLIN, 59 | GST_DRM_CLEARKEY, 60 | GST_DRM_UNKNOWN = -1 61 | } GstCencDrmType; 62 | 63 | typedef struct _GstCencKeyPair 64 | { 65 | GBytes *key_id; 66 | gchar *content_id; 67 | GBytes *key; 68 | } GstCencKeyPair; 69 | 70 | struct _GstCencDecrypt 71 | { 72 | GstBaseTransform parent; 73 | GPtrArray *keys; /* array of GstCencKeyPair objects */ 74 | GstCencDrmType drm_type; 75 | }; 76 | 77 | struct _GstCencDecryptClass 78 | { 79 | GstBaseTransformClass parent_class; 80 | }; 81 | 82 | /* prototypes */ 83 | static void gst_cenc_decrypt_dispose (GObject * object); 84 | static void gst_cenc_decrypt_finalize (GObject * object); 85 | 86 | static gboolean gst_cenc_decrypt_start (GstBaseTransform * trans); 87 | static gboolean gst_cenc_decrypt_stop (GstBaseTransform * trans); 88 | static gboolean gst_cenc_decrypt_append_if_not_duplicate(GstCaps *dest, GstStructure *new_struct); 89 | static GstCaps *gst_cenc_decrypt_transform_caps (GstBaseTransform * base, 90 | GstPadDirection direction, GstCaps * caps, GstCaps * filter); 91 | 92 | static GstFlowReturn gst_cenc_decrypt_transform_ip (GstBaseTransform * trans, 93 | GstBuffer * buf); 94 | static const GstCencKeyPair* gst_cenc_decrypt_lookup_key (GstCencDecrypt * self, 95 | GstBuffer * kid); 96 | static GstCencKeyPair* gst_cenc_decrypt_get_key (GstCencDecrypt * self, GstBuffer * kid); 97 | static gboolean gst_cenc_decrypt_sink_event_handler (GstBaseTransform * trans, 98 | GstEvent * event); 99 | static gchar* gst_cenc_create_uuid_string (gconstpointer uuid_bytes); 100 | 101 | #define M_MPD_PROTECTION_ID "5e629af5-38da-4063-8977-97ffbd9902d4" 102 | #define M_PSSH_PROTECTION_ID "69f908af-4816-46ea-910c-cd5dcccb0a3a" 103 | #define CLEARKEY_PROTECTION_ID "e2719d58-a985-b3c9-781a-b030af78d30e" 104 | 105 | /* pad templates */ 106 | 107 | static GstStaticPadTemplate gst_cenc_decrypt_sink_template = 108 | GST_STATIC_PAD_TEMPLATE ("sink", 109 | GST_PAD_SINK, 110 | GST_PAD_ALWAYS, 111 | GST_STATIC_CAPS 112 | ( 113 | "application/x-cenc, protection-system=(string)" CLEARKEY_PROTECTION_ID "; " 114 | "application/x-cenc, protection-system=(string)" M_MPD_PROTECTION_ID "; " 115 | "application/x-cenc, protection-system=(string)" M_PSSH_PROTECTION_ID) 116 | ); 117 | 118 | static GstStaticPadTemplate gst_cenc_decrypt_src_template = 119 | GST_STATIC_PAD_TEMPLATE ("src", 120 | GST_PAD_SRC, 121 | GST_PAD_ALWAYS, 122 | GST_STATIC_CAPS_ANY 123 | ); 124 | 125 | 126 | static const gchar* gst_cenc_decrypt_protection_ids[] = { 127 | CLEARKEY_PROTECTION_ID, 128 | M_MPD_PROTECTION_ID, 129 | M_PSSH_PROTECTION_ID, 130 | NULL 131 | }; 132 | 133 | /* class initialization */ 134 | 135 | #define gst_cenc_decrypt_parent_class parent_class 136 | G_DEFINE_TYPE (GstCencDecrypt, gst_cenc_decrypt, GST_TYPE_BASE_TRANSFORM); 137 | 138 | static void gst_cenc_keypair_destroy (gpointer data); 139 | 140 | static void 141 | gst_cenc_decrypt_class_init (GstCencDecryptClass * klass) 142 | { 143 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); 144 | GstBaseTransformClass *base_transform_class = 145 | GST_BASE_TRANSFORM_CLASS (klass); 146 | GstElementClass *element_class = GST_ELEMENT_CLASS (klass); 147 | 148 | gst_element_class_add_pad_template (element_class, 149 | gst_static_pad_template_get (&gst_cenc_decrypt_sink_template)); 150 | gst_element_class_add_pad_template (element_class, 151 | gst_static_pad_template_get (&gst_cenc_decrypt_src_template)); 152 | 153 | gst_element_class_set_static_metadata (element_class, 154 | "Decrypt content encrypted using ISOBMFF Common Encryption", 155 | GST_ELEMENT_FACTORY_KLASS_DECRYPTOR, 156 | "Decrypts media that has been encrypted using ISOBMFF Common Encryption.", 157 | "Alex Ashley "); 158 | 159 | GST_DEBUG_CATEGORY_INIT (gst_cenc_decrypt_debug_category, 160 | "cencdec", 0, "CENC decryptor"); 161 | 162 | gobject_class->dispose = gst_cenc_decrypt_dispose; 163 | gobject_class->finalize = gst_cenc_decrypt_finalize; 164 | base_transform_class->start = GST_DEBUG_FUNCPTR (gst_cenc_decrypt_start); 165 | base_transform_class->stop = GST_DEBUG_FUNCPTR (gst_cenc_decrypt_stop); 166 | base_transform_class->transform_ip = 167 | GST_DEBUG_FUNCPTR (gst_cenc_decrypt_transform_ip); 168 | base_transform_class->transform_caps = 169 | GST_DEBUG_FUNCPTR (gst_cenc_decrypt_transform_caps); 170 | base_transform_class->sink_event = 171 | GST_DEBUG_FUNCPTR (gst_cenc_decrypt_sink_event_handler); 172 | base_transform_class->transform_ip_on_passthrough = FALSE; 173 | } 174 | 175 | static void 176 | gst_cenc_decrypt_init (GstCencDecrypt * self) 177 | { 178 | GstBaseTransform *base = GST_BASE_TRANSFORM (self); 179 | 180 | GST_PAD_SET_ACCEPT_TEMPLATE (GST_BASE_TRANSFORM_SINK_PAD (self)); 181 | 182 | gst_base_transform_set_in_place (base, TRUE); 183 | gst_base_transform_set_passthrough (base, FALSE); 184 | gst_base_transform_set_gap_aware (GST_BASE_TRANSFORM (self), FALSE); 185 | self->keys = g_ptr_array_new_with_free_func (gst_cenc_keypair_destroy); 186 | self->drm_type = GST_DRM_UNKNOWN; 187 | } 188 | 189 | void 190 | gst_cenc_decrypt_dispose (GObject * object) 191 | { 192 | GstCencDecrypt *self = GST_CENC_DECRYPT (object); 193 | 194 | if (self->keys) { 195 | g_ptr_array_unref (self->keys); 196 | self->keys = NULL; 197 | } 198 | 199 | G_OBJECT_CLASS (parent_class)->dispose (object); 200 | } 201 | 202 | void 203 | gst_cenc_decrypt_finalize (GObject * object) 204 | { 205 | /* GstCencDecrypt *self = GST_CENC_DECRYPT (object); */ 206 | 207 | /* clean up object here */ 208 | 209 | G_OBJECT_CLASS (parent_class)->finalize (object); 210 | } 211 | 212 | static gboolean 213 | gst_cenc_decrypt_start (GstBaseTransform * trans) 214 | { 215 | GstCencDecrypt *self = GST_CENC_DECRYPT (trans); 216 | GST_DEBUG_OBJECT (self, "start"); 217 | return TRUE; 218 | } 219 | 220 | static gboolean 221 | gst_cenc_decrypt_stop (GstBaseTransform * trans) 222 | { 223 | GstCencDecrypt *self = GST_CENC_DECRYPT (trans); 224 | GST_DEBUG_OBJECT (self, "stop"); 225 | return TRUE; 226 | } 227 | 228 | /* 229 | Append new_structure to dest, but only if it does not already exist in res. 230 | This function takes ownership of new_structure. 231 | */ 232 | static gboolean 233 | gst_cenc_decrypt_append_if_not_duplicate(GstCaps *dest, GstStructure *new_struct) 234 | { 235 | gboolean duplicate=FALSE; 236 | gint j; 237 | 238 | for (j = 0; !duplicate && j < gst_caps_get_size (dest); ++j) { 239 | GstStructure *s = gst_caps_get_structure (dest, j); 240 | if(gst_structure_is_equal (s,new_struct)){ 241 | duplicate=TRUE; 242 | } 243 | } 244 | if(!duplicate){ 245 | gst_caps_append_structure (dest, new_struct); 246 | } 247 | else{ 248 | gst_structure_free (new_struct); 249 | } 250 | return duplicate; 251 | } 252 | 253 | /* filter out the audio and video related fields from the up-stream caps, 254 | because they are not relevant to the input caps of this element and 255 | can cause caps negotiation failures with adaptive bitrate streams */ 256 | static void 257 | gst_cenc_remove_codec_fields (GstStructure *gs) 258 | { 259 | gint j, n_fields = gst_structure_n_fields (gs); 260 | for(j=n_fields-1; j>=0; --j){ 261 | const gchar *field_name; 262 | 263 | field_name = gst_structure_nth_field_name (gs, j); 264 | GST_TRACE ("Check field \"%s\" for removal", field_name); 265 | 266 | if( g_strcmp0 (field_name, "base-profile")==0 || 267 | g_strcmp0 (field_name, "codec_data")==0 || 268 | g_strcmp0 (field_name, "height")==0 || 269 | g_strcmp0 (field_name, "framerate")==0 || 270 | g_strcmp0 (field_name, "level")==0 || 271 | g_strcmp0 (field_name, "pixel-aspect-ratio")==0 || 272 | g_strcmp0 (field_name, "profile")==0 || 273 | g_strcmp0 (field_name, "rate")==0 || 274 | g_strcmp0 (field_name, "width")==0 ){ 275 | gst_structure_remove_field (gs, field_name); 276 | GST_TRACE ("Removing field %s", field_name); 277 | } 278 | } 279 | } 280 | 281 | /* 282 | Given the pad in this direction and the given caps, what caps are allowed on 283 | the other pad in this element ? 284 | */ 285 | static GstCaps * 286 | gst_cenc_decrypt_transform_caps (GstBaseTransform * base, 287 | GstPadDirection direction, GstCaps * caps, GstCaps * filter) 288 | { 289 | GstCaps *res = NULL; 290 | gint i, j; 291 | 292 | g_return_val_if_fail (direction != GST_PAD_UNKNOWN, NULL); 293 | 294 | GST_DEBUG_OBJECT (base, "direction: %s caps: %" GST_PTR_FORMAT " filter:" 295 | " %" GST_PTR_FORMAT, (direction == GST_PAD_SRC) ? "Src" : "Sink", 296 | caps, filter); 297 | 298 | if(direction == GST_PAD_SRC && gst_caps_is_any (caps)){ 299 | res = gst_pad_get_pad_template_caps (GST_BASE_TRANSFORM_SINK_PAD (base)); 300 | goto filter; 301 | } 302 | 303 | res = gst_caps_new_empty (); 304 | 305 | for (i = 0; i < gst_caps_get_size (caps); ++i) { 306 | GstStructure *in = gst_caps_get_structure (caps, i); 307 | GstStructure *out = NULL; 308 | 309 | if (direction == GST_PAD_SINK) { 310 | gint n_fields; 311 | 312 | if (!gst_structure_has_field (in, "original-media-type")) 313 | continue; 314 | 315 | out = gst_structure_copy (in); 316 | n_fields = gst_structure_n_fields (in); 317 | 318 | gst_structure_set_name (out, 319 | gst_structure_get_string (out, "original-media-type")); 320 | 321 | /* filter out the DRM related fields from the down-stream caps */ 322 | for(j=n_fields-1; j>=0; --j){ 323 | const gchar *field_name; 324 | 325 | field_name = gst_structure_nth_field_name (in, j); 326 | 327 | if( g_str_has_prefix(field_name, "protection-system") || 328 | g_str_has_prefix(field_name, "original-media-type") ){ 329 | gst_structure_remove_field (out, field_name); 330 | } 331 | } 332 | gst_cenc_decrypt_append_if_not_duplicate(res, out); 333 | } else { /* GST_PAD_SRC */ 334 | gint n_fields; 335 | GstStructure *tmp = NULL; 336 | guint p; 337 | tmp = gst_structure_copy (in); 338 | gst_cenc_remove_codec_fields (tmp); 339 | for(p=0; gst_cenc_decrypt_protection_ids[p]; ++p){ 340 | /* filter out the audio/video related fields from the down-stream 341 | caps, because they are not relevant to the input caps of this 342 | element and they can cause caps negotiation failures with 343 | adaptive bitrate streams */ 344 | out = gst_structure_copy (tmp); 345 | gst_structure_set (out, 346 | "protection-system", G_TYPE_STRING, gst_cenc_decrypt_protection_ids[p], 347 | "original-media-type", G_TYPE_STRING, gst_structure_get_name (in), 348 | NULL); 349 | gst_structure_set_name (out, "application/x-cenc"); 350 | gst_cenc_decrypt_append_if_not_duplicate(res, out); 351 | } 352 | gst_structure_free (tmp); 353 | } 354 | } 355 | if(direction == GST_PAD_SINK && gst_caps_get_size (res)==0){ 356 | gst_caps_unref (res); 357 | res = gst_caps_new_any (); 358 | } 359 | filter: 360 | if (filter) { 361 | GstCaps *intersection; 362 | 363 | GST_DEBUG_OBJECT (base, "Using filter caps %" GST_PTR_FORMAT, filter); 364 | intersection = 365 | gst_caps_intersect_full (res, filter, GST_CAPS_INTERSECT_FIRST); 366 | gst_caps_unref (res); 367 | res = intersection; 368 | } 369 | 370 | GST_DEBUG_OBJECT (base, "returning %" GST_PTR_FORMAT, res); 371 | return res; 372 | } 373 | 374 | static gchar * 375 | gst_cenc_bytes_to_hexstring (gconstpointer bytes, guint length) 376 | { 377 | const guint8 *data = (const guint8 *) bytes; 378 | gchar *string = g_malloc0 ((2 * length) + 1); 379 | guint i; 380 | 381 | for (i = 0; i < length; ++i) { 382 | g_snprintf (string + (2 * i), 3, "%02x", data[i]); 383 | } 384 | 385 | return string; 386 | } 387 | 388 | static gchar * 389 | gst_cenc_create_content_id (GstCencDecrypt * self, gconstpointer key_id) 390 | { 391 | const guint8 *id = (const guint8 *) key_id; 392 | const gsize id_string_length = 48; /* Length of Content ID string */ 393 | gchar *id_string = g_malloc0 (id_string_length); 394 | gchar *prefix = self->drm_type==GST_DRM_MARLIN ? "urn:marlin:kid:" : ""; 395 | 396 | g_snprintf (id_string, id_string_length, 397 | "%s%02x%02x%02x%02x%02x%02x%02x%02x" 398 | "%02x%02x%02x%02x%02x%02x%02x%02x", 399 | prefix, 400 | id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], 401 | id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]); 402 | 403 | return id_string; 404 | } 405 | 406 | static GstBuffer* 407 | gst_cenc_decrypt_key_id_from_content_id(GstCencDecrypt * self, const gchar *content_id) 408 | { 409 | GstBuffer *kid; 410 | GstMapInfo map; 411 | gboolean failed=FALSE; 412 | guint i,pos; 413 | /*gchar *id_string;*/ 414 | 415 | if(!g_str_has_prefix (content_id, "urn:marlin:kid:")){ 416 | return NULL; 417 | } 418 | kid = gst_buffer_new_allocate (NULL, KID_LENGTH, NULL); 419 | gst_buffer_map (kid, &map, GST_MAP_READWRITE); 420 | for(i=0, pos=strlen("urn:marlin:kid:"); ikey_id = g_bytes_new (info.data, KID_LENGTH); 456 | kp->content_id = gst_cenc_create_content_id (self, info.data); 457 | gst_buffer_unmap(key_id, &info); 458 | 459 | GST_DEBUG_OBJECT (self, "Content ID: %s", kp->content_id); 460 | 461 | if (self->drm_type == GST_DRM_MARLIN) { 462 | /* Perform sha1 hash of content id. */ 463 | SHA1 ((const unsigned char *) kp->content_id, 47, hash); 464 | hash_string = gst_cenc_bytes_to_hexstring (hash, SHA_DIGEST_LENGTH); 465 | } 466 | else { 467 | /* content_id is a hex representation of the KID */ 468 | hash_string = g_strdup (kp->content_id); 469 | } 470 | GST_DEBUG_OBJECT (self, "Hash: %s", hash_string); 471 | 472 | /* Read contents of file with the hash as its name. */ 473 | path = g_strconcat ("/tmp/", hash_string, ".key", NULL); 474 | g_free (hash_string); 475 | GST_DEBUG_OBJECT (self, "Opening file: %s", path); 476 | key_file = fopen (path, "rb"); 477 | 478 | if (!key_file) { 479 | GST_ERROR_OBJECT (self, "Failed to open keyfile: %s", path); 480 | goto error; 481 | } 482 | 483 | bytes_read = fread (key, 1, KEY_LENGTH, key_file); 484 | fclose (key_file); 485 | 486 | if (bytes_read != KEY_LENGTH) { 487 | GST_ERROR_OBJECT (self, "Failed to read key from file %s", path); 488 | goto error; 489 | } 490 | g_free (path); 491 | 492 | kp->key = g_bytes_new (key, KEY_LENGTH); 493 | g_ptr_array_add (self->keys, kp); 494 | 495 | return kp; 496 | error: 497 | g_free (path); 498 | gst_cenc_keypair_destroy (kp); 499 | return NULL; 500 | } 501 | 502 | static gchar * 503 | gst_cenc_create_uuid_string (gconstpointer uuid_bytes) 504 | { 505 | const guint8 *uuid = (const guint8 *) uuid_bytes; 506 | const gsize uuid_string_length = 37; /* Length of UUID string */ 507 | gchar *uuid_string = g_malloc0 (uuid_string_length); 508 | 509 | g_snprintf (uuid_string, uuid_string_length, 510 | "%02x%02x%02x%02x-%02x%02x-%02x%02x-" 511 | "%02x%02x-%02x%02x%02x%02x%02x%02x", 512 | uuid[0], uuid[1], uuid[2], uuid[3], 513 | uuid[4], uuid[5], uuid[6], uuid[7], 514 | uuid[8], uuid[9], uuid[10], uuid[11], 515 | uuid[12], uuid[13], uuid[14], uuid[15]); 516 | 517 | return uuid_string; 518 | } 519 | 520 | static const GstCencKeyPair* 521 | gst_cenc_decrypt_lookup_key (GstCencDecrypt * self, GstBuffer * kid) 522 | { 523 | GstMapInfo info; 524 | const GstCencKeyPair *kp=NULL; 525 | int i; 526 | gsize sz; 527 | 528 | /* 529 | GstMapInfo info; 530 | gchar *id_string; 531 | gst_buffer_map (kid, &info, GST_MAP_READ); 532 | id_string = gst_cenc_create_uuid_string (info.data); 533 | GST_DEBUG_OBJECT (self, "Looking up key ID: %s", id_string); 534 | g_free (id_string); 535 | gst_buffer_unmap (kid, &info); 536 | */ 537 | for (i = 0; kp==NULL && i < self->keys->len; ++i) { 538 | const GstCencKeyPair *k; 539 | k = g_ptr_array_index (self->keys, i); 540 | if(gst_buffer_memcmp (kid, 0, g_bytes_get_data (k->key_id, NULL), KEY_LENGTH)==0){ 541 | kp=k; 542 | } 543 | } 544 | if (!kp) { 545 | kp = gst_cenc_decrypt_get_key (self, kid); 546 | } 547 | 548 | return kp; 549 | } 550 | 551 | static GstFlowReturn 552 | gst_cenc_decrypt_transform_ip (GstBaseTransform * base, GstBuffer * buf) 553 | { 554 | GstCencDecrypt *self = GST_CENC_DECRYPT (base); 555 | GstFlowReturn ret = GST_FLOW_OK; 556 | GstMapInfo map, iv_map; 557 | const GstCencKeyPair *keypair; 558 | const GstProtectionMeta *prot_meta = NULL; 559 | guint pos = 0; 560 | gint sample_index = 0; 561 | guint subsample_count; 562 | AesCtrState *state = NULL; 563 | guint iv_size; 564 | gboolean encrypted; 565 | const GValue *value; 566 | GstBuffer *key_id = NULL; 567 | GstBuffer *iv_buf = NULL; 568 | GBytes *iv_bytes = NULL; 569 | GstBuffer *subsamples_buf = NULL; 570 | GstMapInfo subsamples_map; 571 | GstByteReader *reader=NULL; 572 | 573 | GST_TRACE_OBJECT (self, "decrypt in-place"); 574 | prot_meta = (GstProtectionMeta*) gst_buffer_get_protection_meta (buf); 575 | if (!prot_meta || !buf) { 576 | if (!prot_meta) { 577 | GST_ERROR_OBJECT (self, "Failed to get GstProtection metadata from buffer"); 578 | } 579 | if (!buf) { 580 | GST_ERROR_OBJECT (self, "Failed to get writable buffer"); 581 | } 582 | ret = GST_FLOW_NOT_SUPPORTED; 583 | goto out; 584 | } 585 | 586 | if (!gst_buffer_map (buf, &map, GST_MAP_READWRITE)) { 587 | GST_ERROR_OBJECT (self, "Failed to map buffer"); 588 | ret = GST_FLOW_NOT_SUPPORTED; 589 | goto release; 590 | } 591 | 592 | GST_TRACE_OBJECT (self, "decrypt sample %d", (gint)map.size); 593 | if(!gst_structure_get_uint(prot_meta->info,"iv_size",&iv_size)){ 594 | GST_ERROR_OBJECT (self, "failed to get iv_size"); 595 | ret = GST_FLOW_NOT_SUPPORTED; 596 | goto release; 597 | } 598 | if(!gst_structure_get_boolean(prot_meta->info,"encrypted",&encrypted)){ 599 | GST_ERROR_OBJECT (self, "failed to get encrypted flag"); 600 | ret = GST_FLOW_NOT_SUPPORTED; 601 | goto release; 602 | } 603 | if (iv_size == 0 || !encrypted) { 604 | /* sample is not encrypted */ 605 | goto beach; 606 | } 607 | GST_DEBUG_OBJECT (base, "protection meta: %" GST_PTR_FORMAT, prot_meta->info); 608 | if(!gst_structure_get_uint(prot_meta->info,"subsample_count",&subsample_count)){ 609 | GST_ERROR_OBJECT (self, "failed to get subsample_count"); 610 | ret = GST_FLOW_NOT_SUPPORTED; 611 | goto release; 612 | } 613 | value = gst_structure_get_value (prot_meta->info, "kid"); 614 | if(!value){ 615 | GST_ERROR_OBJECT (self, "Failed to get KID for sample"); 616 | ret = GST_FLOW_NOT_SUPPORTED; 617 | goto release; 618 | } 619 | key_id = gst_value_get_buffer (value); 620 | 621 | value = gst_structure_get_value (prot_meta->info, "iv"); 622 | if(!value){ 623 | GST_ERROR_OBJECT (self, "Failed to get IV for sample"); 624 | ret = GST_FLOW_NOT_SUPPORTED; 625 | goto release; 626 | } 627 | iv_buf = gst_value_get_buffer (value); 628 | if(!gst_buffer_map (iv_buf, &iv_map, GST_MAP_READ)){ 629 | GST_ERROR_OBJECT (self, "Failed to map IV"); 630 | ret = GST_FLOW_NOT_SUPPORTED; 631 | goto release; 632 | } 633 | iv_bytes = g_bytes_new (iv_map.data, iv_map.size); 634 | gst_buffer_unmap (iv_buf, &iv_map); 635 | if(subsample_count){ 636 | value = gst_structure_get_value (prot_meta->info, "subsamples"); 637 | if(!value){ 638 | GST_ERROR_OBJECT (self, "Failed to get subsamples"); 639 | ret = GST_FLOW_NOT_SUPPORTED; 640 | goto release; 641 | } 642 | subsamples_buf = gst_value_get_buffer (value); 643 | if(!gst_buffer_map (subsamples_buf, &subsamples_map, GST_MAP_READ)){ 644 | GST_ERROR_OBJECT (self, "Failed to map subsample buffer"); 645 | ret = GST_FLOW_NOT_SUPPORTED; 646 | goto release; 647 | } 648 | } 649 | 650 | keypair = gst_cenc_decrypt_lookup_key (self,key_id); 651 | 652 | if (!keypair) { 653 | gsize sz; 654 | GST_ERROR_OBJECT (self, "Failed to lookup key"); 655 | GST_MEMDUMP_OBJECT (self, "Key ID:", 656 | g_bytes_get_data (keypair->key_id, &sz), 657 | sz); 658 | ret = GST_FLOW_NOT_SUPPORTED; 659 | goto release; 660 | } 661 | 662 | state = gst_aes_ctr_decrypt_new (keypair->key, iv_bytes); 663 | 664 | if (!state) { 665 | GST_ERROR_OBJECT (self, "Failed to init AES cipher"); 666 | ret = GST_FLOW_NOT_SUPPORTED; 667 | goto release; 668 | } 669 | 670 | if (subsample_count) { 671 | reader = gst_byte_reader_new (subsamples_map.data, subsamples_map.size); 672 | if(!reader){ 673 | GST_ERROR_OBJECT (self, "Failed to allocate subsample reader"); 674 | ret = GST_FLOW_NOT_SUPPORTED; 675 | goto release; 676 | } 677 | } 678 | 679 | while (pos < map.size) { 680 | guint16 n_bytes_clear = 0; 681 | guint32 n_bytes_encrypted = 0; 682 | 683 | if (sample_index < subsample_count) { 684 | if (!gst_byte_reader_get_uint16_be (reader, &n_bytes_clear) 685 | || !gst_byte_reader_get_uint32_be (reader, &n_bytes_encrypted)) { 686 | ret = GST_FLOW_NOT_SUPPORTED; 687 | goto release; 688 | } 689 | sample_index++; 690 | } else { 691 | n_bytes_clear = 0; 692 | n_bytes_encrypted = map.size - pos; 693 | } 694 | GST_TRACE_OBJECT (self, "%u bytes clear (todo=%d)", n_bytes_clear, 695 | (gint)map.size - pos); 696 | pos += n_bytes_clear; 697 | if (n_bytes_encrypted) { 698 | GST_TRACE_OBJECT (self, "%u bytes encrypted (todo=%d)", 699 | n_bytes_encrypted, (gint)map.size - pos); 700 | gst_aes_ctr_decrypt_ip (state, map.data + pos, n_bytes_encrypted); 701 | pos += n_bytes_encrypted; 702 | } 703 | } 704 | 705 | beach: 706 | gst_buffer_unmap (buf, &map); 707 | if (state) { 708 | gst_aes_ctr_decrypt_unref (state); 709 | } 710 | release: 711 | if (reader){ 712 | gst_byte_reader_free (reader); 713 | } 714 | if(subsamples_buf){ 715 | gst_buffer_unmap (subsamples_buf, &subsamples_map); 716 | } 717 | if (prot_meta) { 718 | gst_buffer_remove_meta (buf, (GstMeta *) prot_meta); 719 | } 720 | if (iv_bytes) { 721 | g_bytes_unref (iv_bytes); 722 | } 723 | out: 724 | return ret; 725 | } 726 | 727 | static void 728 | gst_cenc_decrypt_parse_pssh_box (GstCencDecrypt * self, GstBuffer * pssh) 729 | { 730 | GstMapInfo info; 731 | GstByteReader br; 732 | guint8 version; 733 | guint32 data_size; 734 | 735 | gst_buffer_map (pssh, &info, GST_MAP_READ); 736 | gst_byte_reader_init (&br, info.data, info.size); 737 | 738 | gst_byte_reader_skip_unchecked (&br, 8); 739 | version = gst_byte_reader_get_uint8_unchecked (&br); 740 | GST_DEBUG_OBJECT (self, "pssh version: %u", version); 741 | gst_byte_reader_skip_unchecked (&br, 19); 742 | 743 | if (version > 0) { 744 | /* Parse KeyIDs */ 745 | guint32 key_id_count = 0; 746 | const guint8 *key_id_data = NULL; 747 | const guint key_id_size = 16; 748 | 749 | key_id_count = gst_byte_reader_get_uint32_be_unchecked (&br); 750 | GST_DEBUG_OBJECT (self, "there are %u key IDs", key_id_count); 751 | key_id_data = gst_byte_reader_get_data_unchecked (&br, key_id_count * 16); 752 | 753 | while (key_id_count > 0) { 754 | gchar *key_id_string = gst_cenc_create_uuid_string (key_id_data); 755 | GST_DEBUG_OBJECT (self, "key_id: %s", key_id_string); 756 | g_free (key_id_string); 757 | key_id_data += key_id_size; 758 | --key_id_count; 759 | } 760 | } 761 | 762 | /* Parse Data */ 763 | data_size = gst_byte_reader_get_uint32_be_unchecked (&br); 764 | GST_DEBUG_OBJECT (self, "pssh data size: %u", data_size); 765 | 766 | if (data_size > 0U) { 767 | gpointer data = 768 | g_memdup (gst_byte_reader_get_data_unchecked (&br, data_size), 769 | data_size); 770 | GstBuffer *buf = gst_buffer_new_wrapped (data, data_size); 771 | GST_DEBUG_OBJECT (self, "cenc protection system data size: %" 772 | G_GSIZE_FORMAT, gst_buffer_get_size (buf)); 773 | gst_buffer_unref (buf); 774 | } 775 | gst_buffer_unmap (pssh, &info); 776 | } 777 | 778 | static gboolean 779 | gst_cenc_decrypt_parse_content_protection_element (GstCencDecrypt * self, 780 | GstBuffer * pssi) 781 | { 782 | GstMapInfo info; 783 | guint32 data_size; 784 | xmlDocPtr doc; 785 | xmlNode *root_element = NULL; 786 | gboolean ret = TRUE; 787 | xmlNode *cur_node; 788 | 789 | gst_buffer_map (pssi, &info, GST_MAP_READ); 790 | 791 | /* this initialize the library and check potential ABI mismatches 792 | * between the version it was compiled for and the actual shared 793 | * library used 794 | */ 795 | LIBXML_TEST_VERSION 796 | /* parse "data" into a document (which is a libxml2 tree structure xmlDoc) */ 797 | doc = 798 | xmlReadMemory (info.data, info.size, "ContentProtection.xml", NULL, XML_PARSE_NONET); 799 | if (!doc) { 800 | ret = FALSE; 801 | GST_ERROR_OBJECT (self, "Failed to parse XML from pssi event"); 802 | goto beach; 803 | } 804 | root_element = xmlDocGetRootElement (doc); 805 | 806 | if (root_element->type != XML_ELEMENT_NODE 807 | || xmlStrcmp (root_element->name, (xmlChar *) "ContentProtection") != 0) { 808 | GST_ERROR_OBJECT (self, "Failed to find ContentProtection element"); 809 | ret = FALSE; 810 | goto beach; 811 | } 812 | 813 | /* Parse KeyIDs */ 814 | for (cur_node = root_element->children; cur_node; cur_node = cur_node->next) { 815 | xmlNode *k_node; 816 | if (cur_node->type != XML_ELEMENT_NODE || 817 | !g_str_has_suffix ((const gchar *)cur_node->name,"MarlinContentIds")) 818 | continue; 819 | for (k_node = cur_node->children; k_node; k_node = k_node->next) { 820 | xmlChar *node_content; 821 | GstBuffer *kid; 822 | GstMapInfo map; 823 | if (k_node->type != XML_ELEMENT_NODE || 824 | !g_str_has_suffix ((const gchar*)k_node->name, "MarlinContentId")) 825 | continue; 826 | node_content = xmlNodeGetContent (k_node); 827 | if (!node_content) 828 | continue; 829 | GST_DEBUG_OBJECT (self, "ContentId: %s", node_content); 830 | kid = gst_cenc_decrypt_key_id_from_content_id(self, node_content); 831 | /* pre-fetch the key */ 832 | if(kid && !gst_cenc_decrypt_get_key (self, kid)){ 833 | GST_ERROR_OBJECT (self, "Failed to get key for content ID %s", node_content); 834 | } 835 | if(kid) 836 | gst_buffer_unref (kid); 837 | xmlFree (node_content); 838 | } 839 | } 840 | 841 | beach: 842 | gst_buffer_unmap (pssi, &info); 843 | if (doc) 844 | xmlFreeDoc (doc); 845 | return (ret); 846 | } 847 | 848 | static gboolean 849 | gst_cenc_decrypt_sink_event_handler (GstBaseTransform * trans, GstEvent * event) 850 | { 851 | gboolean ret = TRUE; 852 | const gchar *system_id; 853 | GstBuffer *pssi = NULL; 854 | const gchar *loc; 855 | GstCencDecrypt *self = GST_CENC_DECRYPT (trans); 856 | 857 | switch (GST_EVENT_TYPE (event)) { 858 | case GST_EVENT_PROTECTION: 859 | GST_DEBUG_OBJECT (self, "received protection event"); 860 | gst_event_parse_protection (event, &system_id, &pssi, &loc); 861 | GST_DEBUG_OBJECT (self, "system_id: %s loc: %s", system_id, loc); 862 | if(g_ascii_strcasecmp(loc, "dash/mpd")==0 && g_ascii_strcasecmp(system_id, M_MPD_PROTECTION_ID)==0){ 863 | GST_DEBUG_OBJECT (self, "event carries MPD pssi data"); 864 | self->drm_type = GST_DRM_MARLIN; 865 | gst_cenc_decrypt_parse_content_protection_element (self, pssi); 866 | } 867 | else if(g_ascii_strcasecmp(loc, "dash/mpd")==0 && g_ascii_strcasecmp(system_id, M_MPD_PROTECTION_ID)==0){ 868 | GST_DEBUG_OBJECT (self, "event carries MPD clearkey data"); 869 | self->drm_type = GST_DRM_CLEARKEY; 870 | /* TODO: parse clearkey:Laurl element */ 871 | } 872 | else if(g_str_has_prefix (loc, "isobmff/") && g_ascii_strcasecmp(system_id, M_PSSH_PROTECTION_ID)==0){ 873 | GST_DEBUG_OBJECT (self, "event carries pssh data from qtdemux"); 874 | self->drm_type = GST_DRM_MARLIN; 875 | gst_cenc_decrypt_parse_pssh_box (self, pssi); 876 | } 877 | gst_event_unref (event); 878 | break; 879 | 880 | default: 881 | ret = GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event); 882 | break; 883 | } 884 | 885 | return ret; 886 | } 887 | 888 | static void gst_cenc_keypair_destroy (gpointer data) 889 | { 890 | GstCencKeyPair *key_pair = (GstCencKeyPair*)data; 891 | g_bytes_unref (key_pair->key_id); 892 | g_free (key_pair->content_id); 893 | g_bytes_unref (key_pair->key); 894 | g_free (key_pair); 895 | } 896 | 897 | --------------------------------------------------------------------------------