[vlc-devel] [PATCH 04/48] hls: Support encryption
Jean-Paul Saman
jpsaman at videolan.org
Tue Jan 10 11:38:25 CET 2012
Hi,
See below for my review comments.
On Mon, Jan 9, 2012 at 4:16 PM, Hugo Beauzée-Luyssen <beauze.h at gmail.com> wrote:
> From: Luc Saillard <luc.saillard at sfr.com>
>
Where is the patch description?
> ---
> modules/stream_filter/Modules.am | 11 +-
> modules/stream_filter/httplive.c | 298 +++++++++++++++++++++++++++++++++++++-
> 2 files changed, 301 insertions(+), 8 deletions(-)
>
> diff --git a/modules/stream_filter/Modules.am b/modules/stream_filter/Modules.am
> index a864eba..4d128fe 100644
> --- a/modules/stream_filter/Modules.am
> +++ b/modules/stream_filter/Modules.am
> @@ -2,12 +2,19 @@ SUBDIRS = dash
>
> SOURCES_decomp = decomp.c
> SOURCES_stream_filter_record = record.c
> -SOURCES_stream_filter_httplive = httplive.c
>
> libvlc_LTLIBRARIES += \
> libstream_filter_record_plugin.la \
> - libstream_filter_httplive_plugin.la \
> $(NULL)
> +
> +if HAVE_GCRYPT
> +libstream_filter_httplive_plugin_la_SOURCES = httplive.c
> +libstream_filter_httplive_plugin_la_CFLAGS = $(AM_CFLAGS) $(GCRYPT_CFLAGS)
> +libstream_filter_httplive_plugin_la_LIBADD = $(AM_LIBADD) $(GCRYPT_LIBS)
> +libstream_filter_httplive_plugin_la_DEPENDENCIES =
> +libvlc_LTLIBRARIES += libstream_filter_httplive_plugin.la
> +endif
> +
> if !HAVE_WIN32
> if !HAVE_WINCE
> libvlc_LTLIBRARIES += libdecomp_plugin.la
> diff --git a/modules/stream_filter/httplive.c b/modules/stream_filter/httplive.c
> index f7aeab1..1df8b62 100644
> --- a/modules/stream_filter/httplive.c
> +++ b/modules/stream_filter/httplive.c
> @@ -35,12 +35,14 @@
> #include <vlc_plugin.h>
>
> #include <assert.h>
> +#include <gcrypt.h>
>
> #include <vlc_threads.h>
> #include <vlc_arrays.h>
> #include <vlc_stream.h>
> #include <vlc_url.h>
> #include <vlc_memory.h>
> +#include <vlc_gcrypt.h>
>
> /*****************************************************************************
> * Module descriptor
> @@ -59,6 +61,7 @@ vlc_module_end()
> /*****************************************************************************
> *
> *****************************************************************************/
> +#define AES_BLOCK_SIZE 16 /* Only support AES-128 */
> typedef struct segment_s
> {
> int sequence; /* unique sequence number */
> @@ -67,6 +70,10 @@ typedef struct segment_s
> uint64_t bandwidth; /* bandwidth usage of segments (bits per second)*/
>
> char *uri;
> + char *psz_key_path; /* url key path */
> + uint8_t psz_AES_key[16]; /* AES-128 */
make this psz_AES_key[AES_BLOCK_SIZE]; since it depends on the define above.
> + bool b_key_loaded;
> +
> vlc_mutex_t lock;
> block_t *data; /* data */
> } segment_t;
> @@ -85,6 +92,10 @@ typedef struct hls_stream_s
> vlc_url_t url; /* uri to m3u8 */
> vlc_mutex_t lock;
> bool b_cache; /* allow caching */
> +
> + char *psz_current_key_path; /* URL path of the encrypted key */
> + uint8_t psz_AES_IV[AES_BLOCK_SIZE]; /* IV used when decypher the block */
> + bool b_iv_loaded;
> } hls_stream_t;
>
> struct stream_sys_t
> @@ -130,6 +141,7 @@ struct stream_sys_t
> bool b_meta; /* meta playlist */
> bool b_live; /* live stream? or vod? */
> bool b_error; /* parsing error */
> + bool b_aesmsg; /* only print one time that the media is encrypted */
> };
>
> /****************************************************************************
> @@ -212,6 +224,7 @@ static hls_stream_t *hls_New(vlc_array_t *hls_stream, const int id, const uint64
> hls->sequence = 0; /* default is 0 */
> hls->version = 1; /* default protocol version */
> hls->b_cache = true;
> + hls->psz_current_key_path = NULL;
> vlc_UrlParse(&hls->url, uri, 0);
> hls->segments = vlc_array_new();
> vlc_array_append(hls_stream, hls);
> @@ -232,6 +245,8 @@ static void hls_Free(hls_stream_t *hls)
> }
> vlc_array_destroy(hls->segments);
> }
> + if(hls->psz_current_key_path)
> + free(hls->psz_current_key_path);
>
> vlc_UrlClean(&hls->url);
> free(hls);
> @@ -341,6 +356,10 @@ static segment_t *segment_New(hls_stream_t* hls, const int duration, const char
> segment->data = NULL;
> vlc_array_append(hls->segments, segment);
> vlc_mutex_init(&segment->lock);
> + segment->b_key_loaded = false;
> + segment->psz_key_path = NULL;
> + if (hls->psz_current_key_path)
> + segment->psz_key_path = strdup(hls->psz_current_key_path);
> return segment;
> }
>
> @@ -350,6 +369,8 @@ static void segment_Free(segment_t *segment)
>
> if (segment->uri)
> free(segment->uri);
> + if (segment->psz_key_path)
> + free(segment->psz_key_path);
> if (segment->data)
> block_Release(segment->data);
> free(segment);
> @@ -461,6 +482,38 @@ static char *parse_Attributes(const char *line, const char *attr)
> return NULL;
> }
>
> +static int hex2int(char c)
> +{
> + if (c >= '0' && c <= '9')
> + return c - '0';
> + if (c >= 'A' && c <= 'F')
> + return c - 'A' + 10;
> + if (c >= 'a' && c <= 'f')
> + return c - 'a' + 10;
> + return -1;
> +}
> +
> +static int string_to_IV(const char *string_hexa, uint8_t iv[AES_BLOCK_SIZE])
> +{
> + const char *p = string_hexa;
> + uint8_t *d = iv;
> + unsigned int c;
> +
> + if (*p++ != '0')
> + return VLC_EGENERIC;
> + if (*p++ != 'x')
> + return VLC_EGENERIC;
> +
> + while (*p && *(p+1))
> + {
> + c = hex2int(*p++) << 4;
> + c |= hex2int(*p++);
> + *d++ = c;
> + }
> +
> + return VLC_SUCCESS;
> +}
> +
> /*
> * Return an uri, that must be free with free().
> */
> @@ -729,6 +782,69 @@ static int parse_Key(stream_t *s, hls_stream_t *hls, char *p_read)
> free(iv);
> }
> }
> + else if (strncasecmp(attr, "AES-128", 7) == 0)
> + {
> + char *value, *uri, *iv, *key_path;
> + if (s->p_sys->b_aesmsg == false)
> + {
> + msg_Info(s, "playback of AES-128 encrypted HTTP Live media detected.");
> + s->p_sys->b_aesmsg = true;
> + }
> + value = uri = parse_Attributes(p_read, "URI");
> + if (value == NULL)
> + {
> + msg_Err(s, "#EXT-X-KEY: URI not found for encrypted HTTP Live media in AES-128");
> + free(attr);
> + return VLC_EGENERIC;
> + }
> +
> + /* Url is put between quotes, remove them */
> + if (*value == '"')
> + {
> + /* We need to strip the "" from the attribute value */
> + unsigned int i;
> + uri = value + 1;
> + for (i=0; i<strlen(uri); i++)
> + {
> + if (uri[i] == '"')
> + {
> + uri[i] = 0;
> + break;
> + }
> + }
> + }
> + hls->psz_current_key_path = relative_URI(uri, hls->uri);
> + free(value);
> +
> + value = iv = parse_Attributes(p_read, "IV");
> + if (iv == NULL)
> + {
> + /*
> + * If the EXT-X-KEY tag does not have the IV attribute, implementations
> + * MUST use the sequence number of the media file as the IV when
> + * encrypting or decrypting that media file. The big-endian binary
> + * representation of the sequence number SHALL be placed in a 16-octet
> + * buffer and padded (on the left) with zeros.
> + */
> + hls->b_iv_loaded = false;
> + }
> + else
> + {
> + /*
> + * If the EXT-X-KEY tag has the IV attribute, implementations MUST use
> + * the attribute value as the IV when encrypting or decrypting with that
> + * key. The value MUST be interpreted as a 128-bit hexadecimal number
> + * and MUST be prefixed with 0x or 0X.
> + */
> +
> + if (string_to_IV(iv, hls->psz_AES_IV) == VLC_EGENERIC)
> + err = VLC_EGENERIC;
> + else
> + hls->b_iv_loaded = true;
> +
> + free(value);
> + }
> + }
> else
> {
> msg_Warn(s, "playback of encrypted HTTP Live media is not supported.");
> @@ -775,9 +891,9 @@ static int parse_Version(stream_t *s, hls_stream_t *hls, char *p_read)
>
> /* Check version */
> hls->version = version;
> - if (hls->version != 1)
> + if (hls->version < 0 || hls->version > 3)
> {
> - msg_Err(s, "#EXT-X-VERSION should be version 1 iso %d", version);
> + msg_Err(s, "#EXT-X-VERSION should be version 1, 2 or 3 iso %d", version);
> return VLC_EGENERIC;
> }
> return VLC_SUCCESS;
> @@ -998,6 +1114,163 @@ static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const
> return err;
> }
>
> +
> +static int hls_DownloadSegmentKey(stream_t *s, segment_t *seg)
> +{
> + stream_sys_t *p_sys = (stream_sys_t *) s->p_sys;
> + vlc_url_t key_path;
> + uint8_t aeskey[32]; /* AES-512 can use up to 32 bytes */
> + ssize_t len;
> +
> + stream_t *p_m3u8 = stream_UrlNew(s, seg->psz_key_path);
> + if (p_m3u8 == NULL)
> + {
> + msg_Err(s, "Failed to load the AES key for segment sequence %d", seg->sequence);
> + return VLC_EGENERIC;
> + }
> +
> + len = stream_Read(p_m3u8, aeskey, sizeof(aeskey));
> + if (len != AES_BLOCK_SIZE)
> + {
> + msg_Err(s, "The AES key loaded doesn't have the right size (%d)", len);
> + stream_Delete(p_m3u8);
> + return VLC_EGENERIC;
> + }
> +
> + memcpy(seg->psz_AES_key, aeskey, AES_BLOCK_SIZE);
> +
> + stream_Delete(p_m3u8);
> +
> + return VLC_SUCCESS;
> +}
> +
> +static int hls_ManageSegmentKeys(stream_t *s, hls_stream_t *hls)
> +{
> + int i, count, i_err;
> + segment_t *seg = NULL, *prev_seg;
> +
> + count = vlc_array_count( hls->segments );
> + i_err = VLC_SUCCESS;
> + for (i=0; i < count; i++)
> + {
> + prev_seg = seg;
> + seg = segment_GetSegment(hls, i);
> + if (seg->psz_key_path == NULL)
> + continue; /* No key to load ? continue */
> + if (seg->b_key_loaded)
> + continue; /* The key is already loaded */
> +
> + /* if the key has not changed, and already available from previous segment,
> + * try to copy it, and don't load the key */
> + if (prev_seg && prev_seg->b_key_loaded && strcmp(seg->psz_key_path, prev_seg->psz_key_path) == 0)
> + {
> + memcpy(seg->psz_AES_key, prev_seg->psz_AES_key, AES_BLOCK_SIZE);
> + seg->b_key_loaded = true;
> + continue;
> + }
> + if (hls_DownloadSegmentKey(s, seg) != VLC_SUCCESS)
> + return VLC_EGENERIC;
> +
> + seg->b_key_loaded = true;
> + }
> + return VLC_SUCCESS;
> +}
Take the segment lock when you are updating its contents.
> +
> +static int hls_DecodeSegmentData(stream_t *s, hls_stream_t *hls, segment_t *segment)
> +{
> + /* Did the segment need to be decoded ? */
> + if (segment->psz_key_path == NULL)
> + return VLC_SUCCESS;
Isn't it simpler to use a boolean b_encrypted (or b_drm) to indicate
an DRM protected segment?
> +
> + /* Do we have loaded the key ? */
> + if (!segment->b_key_loaded)
> + {
> + /* No ? try to download it now */
> + if (hls_ManageSegmentKeys(s, hls) != VLC_SUCCESS)
> + return VLC_EGENERIC;
> + }
> +
> + /* For now, we only decode AES-128 data */
> + gcry_error_t i_gcrypt_err;
> + gcry_cipher_hd_t aes_ctx;
> + /* Setup AES */
> + i_gcrypt_err = gcry_cipher_open( &aes_ctx, GCRY_CIPHER_AES,
> + GCRY_CIPHER_MODE_CBC, 0 );
> + if ( i_gcrypt_err )
> + {
> + msg_Err(s, "gcry_cipher_open failed: %s", gpg_strerror( i_gcrypt_err ));
> + gcry_cipher_close( aes_ctx );
> + return VLC_EGENERIC;
> + }
> +
> + /* Set key */
> + i_gcrypt_err = gcry_cipher_setkey( aes_ctx, segment->psz_AES_key,
> + sizeof( segment->psz_AES_key ) );
> + if ( i_gcrypt_err )
> + {
> + msg_Err(s, "gcry_cipher_setkey failed: %s", gpg_strerror( i_gcrypt_err ));
> + gcry_cipher_close( aes_ctx );
> + return VLC_EGENERIC;
> + }
> +
> + if (hls->b_iv_loaded == false)
> + {
> + memset(hls->psz_AES_IV, 0, AES_BLOCK_SIZE);
> + hls->psz_AES_IV[15] = segment->sequence & 0xff;
> + hls->psz_AES_IV[14] = (segment->sequence >> 8)& 0xff;
> + hls->psz_AES_IV[13] = (segment->sequence >> 16)& 0xff;
> + hls->psz_AES_IV[12] = (segment->sequence >> 24)& 0xff;
> + }
> +
> + i_gcrypt_err = gcry_cipher_setiv( aes_ctx, hls->psz_AES_IV,
> + sizeof( hls->psz_AES_IV ) );
> +
> + if ( i_gcrypt_err )
> + {
> + msg_Err(s, "gcry_cipher_setiv failed: %s", gpg_strerror( i_gcrypt_err ));
> + gcry_cipher_close( aes_ctx );
> + return VLC_EGENERIC;
> + }
> +
> + i_gcrypt_err = gcry_cipher_decrypt( aes_ctx,
> + segment->data->p_buffer, /* out */
> + segment->data->i_buffer,
> + NULL, /* in */
> + 0 );
> + if ( i_gcrypt_err )
> + {
> + msg_Err(s, "gcry_cipher_decrypt failed: %s/%s\n", gcry_strsource(i_gcrypt_err), gcry_strerror(i_gcrypt_err));
> + gcry_cipher_close( aes_ctx );
> + return VLC_EGENERIC;
> + }
> + gcry_cipher_close( aes_ctx );
> + /* remove the PKCS#7 padding from the buffer */
> + int pad = segment->data->p_buffer[segment->data->i_buffer-1];
> + if (pad <= 0 || pad > AES_BLOCK_SIZE)
> + {
> + msg_Err(s, "Bad padding character (0x%x), perhaps we failed to decrypt the segment with the correct key", pad);
> + return VLC_EGENERIC;
> + }
> + int count = pad;
> + while (count--)
> + {
> + if (segment->data->p_buffer[segment->data->i_buffer-1-count] != pad)
> + {
> + msg_Err(s, "Bad ending buffer, perhaps we failed to decrypt the segment with the correct key");
> + return VLC_EGENERIC;
> + }
> + }
> +
> + /* not all the data is readable because of padding */
> + segment->data->i_buffer -= pad;
> +
> + return VLC_SUCCESS;
> +}
> +
> +
> +
> +
> +
> static int get_HTTPLiveMetaPlaylist(stream_t *s, vlc_array_t **streams)
> {
> stream_sys_t *p_sys = s->p_sys;
> @@ -1185,7 +1458,7 @@ static int BandwidthAdaptation(stream_t *s, int progid, uint64_t *bandwidth)
> return candidate;
> }
>
> -static int Download(stream_t *s, hls_stream_t *hls, segment_t *segment, int *cur_stream)
> +static int hls_DownloadSegmentData(stream_t *s, hls_stream_t *hls, segment_t *segment, int *cur_stream)
Renaming of Download() function should not be part of this patch, but
a separate on.
> {
> stream_sys_t *p_sys = s->p_sys;
>
> @@ -1222,6 +1495,13 @@ static int Download(stream_t *s, hls_stream_t *hls, segment_t *segment, int *cur
> }
> mtime_t duration = mdate() - start;
>
> + /* If the segment is encrypted, decode it */
if (segment->b_drm)
> + if (hls_DecodeSegmentData(s, hls, segment) != VLC_SUCCESS)
> + {
> + vlc_mutex_unlock(&segment->lock);
> + return VLC_EGENERIC;
> + }
> +
> vlc_mutex_unlock(&segment->lock);
>
> msg_Info(s, "downloaded segment %d from stream %d",
> @@ -1306,7 +1586,7 @@ static void* hls_Thread(void *p_this)
> msg_Dbg(s, "Downloading segment %d from stream %d (aka %s)", p_sys->download.segment, p_sys->download.stream, segment->uri);
>
> if ((segment != NULL) &&
> - (Download(s, hls, segment, &p_sys->download.stream) != VLC_SUCCESS))
> + (hls_DownloadSegmentData(s, hls, segment, &p_sys->download.stream) != VLC_SUCCESS))
> {
> if (!vlc_object_alive(s)) break;
>
> @@ -1405,7 +1685,7 @@ again:
> if (segment == NULL )
> return VLC_EGENERIC;
>
> - if (Download(s, hls, segment, current) != VLC_SUCCESS)
> + if (hls_DownloadSegmentData(s, hls, segment, current) != VLC_SUCCESS)
> return VLC_EGENERIC;
>
> /* Found better bandwidth match, try again */
> @@ -1426,7 +1706,7 @@ again:
> continue;
> }
>
> - if (Download(s, hls, segment, current) != VLC_SUCCESS)
> + if (hls_DownloadSegmentData(s, hls, segment, current) != VLC_SUCCESS)
> return VLC_EGENERIC;
>
> p_sys->download.segment++;
> @@ -1605,6 +1885,9 @@ static int Open(vlc_object_t *p_this)
>
> msg_Info(p_this, "HTTP Live Streaming (%s)", s->psz_path);
>
> + /* Initialize crypto bit */
> + vlc_gcrypt_init();
> +
> /* */
> s->p_sys = p_sys = calloc(1, sizeof(*p_sys));
> if (p_sys == NULL)
> @@ -1651,6 +1934,9 @@ static int Open(vlc_object_t *p_this)
> int current = p_sys->playback.stream = 0;
> p_sys->playback.segment = p_sys->download.segment = ChooseSegment(s, current);
>
> + /* manage encryption key if needed */
> + hls_ManageSegmentKeys(s, hls_Get(p_sys->hls_stream, current));
> +
> if (p_sys->b_live && (p_sys->playback.segment < 0))
> {
> msg_Warn(s, "less data than 3 times 'target duration' available for live playback, playback may stall");
> --
> 1.7.8.3
>
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> http://mailman.videolan.org/listinfo/vlc-devel
Kind regards,
Jean-Paul Saman
More information about the vlc-devel
mailing list