From: Jaroslav Kysela Date: Fri, 20 Nov 2015 13:37:04 +0000 (+0100) Subject: IPTV: HLS - add AES-128 decryption support X-Git-Tag: v4.2.1~1501 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c9ceae35a42d6e5f313766a5deb41c4985601795;p=thirdparty%2Ftvheadend.git IPTV: HLS - add AES-128 decryption support --- diff --git a/src/input/mpegts/iptv/iptv_http.c b/src/input/mpegts/iptv/iptv_http.c index b42e40bc0..cadbdf7e1 100644 --- a/src/input/mpegts/iptv/iptv_http.c +++ b/src/input/mpegts/iptv/iptv_http.c @@ -2,6 +2,7 @@ * IPTV - HTTP/HTTPS handler * * Copyright (C) 2013 Adam Sutton + * Copyright (C) 2014,2015 Jaroslav Kysela * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,23 +22,43 @@ #include "iptv_private.h" #include "http.h" #include "misc/m3u.h" +#include "openssl/aes.h" + +#if AES_BLOCK_SIZE != 16 +#error "Wrong openssl!" +#endif typedef struct http_priv { iptv_mux_t *im; http_client_t *hc; uint8_t started; sbuf_t m3u_sbuf; + sbuf_t key_sbuf; int m3u_header; uint64_t off; char *host_url; int64_t hls_seq; char *hls_url; uint8_t hls_url2; + uint8_t hls_encrypted; + char *hls_url_after_key; + char *hls_key_url; htsmsg_t *hls_m3u; + htsmsg_t *hls_key; uint8_t *hls_si; time_t hls_last_si; + struct { + char tmp[AES_BLOCK_SIZE]; + int tmp_len; + AES_KEY key; + unsigned char iv[AES_BLOCK_SIZE]; + } hls_aes128; } http_priv_t; +/***/ + +static int iptv_http_complete_key ( http_client_t *hc ); + /* * */ @@ -72,7 +93,7 @@ iptv_http_safe_global_lock( iptv_mux_t *im ) static char * iptv_http_get_url( http_priv_t *hp, htsmsg_t *m ) { - htsmsg_t *items, *item, *inf, *sel = NULL; + htsmsg_t *items, *item, *inf, *sel = NULL, *key; htsmsg_field_t *f; int64_t seq, bandwidth, sel_bandwidth = 0; int width, height, sel_width = 0, sel_height = 0; @@ -129,11 +150,18 @@ iptv_http_get_url( http_priv_t *hp, htsmsg_t *m ) } else { s = htsmsg_get_str(item, "m3u-url"); if (s && s[0]) { + key = htsmsg_get_map(item, "x-key"); + if (key && strcmp(htsmsg_get_str(key, "METHOD") ?: "NONE", "NONE") != 0) + key = htsmsg_copy(key); + else + key = NULL; s = strdup(s); htsmsg_field_destroy(items, f); if (hp->hls_url) { hp->hls_url2 = 1; hp->hls_m3u = m; + htsmsg_destroy(hp->hls_key); + hp->hls_key = key; hp->hls_seq++; } return (char *)s; @@ -157,6 +185,18 @@ iptv_http_get_url( http_priv_t *hp, htsmsg_t *m ) return NULL; } +/* + * + */ +static void +iptv_http_data_aes128 ( http_priv_t *hp, sbuf_t *sb, int off ) +{ + unsigned char *in = sb->sb_data + off; + unsigned char *out = in; + size_t size = sb->sb_ptr - off; + + AES_cbc_encrypt(in, out, size, &hp->hls_aes128.key, hp->hls_aes128.iv, AES_DECRYPT); +} /* * Connected @@ -194,6 +234,8 @@ iptv_http_header ( http_client_t *hc ) } hp->m3u_header++; sbuf_reset(&hp->m3u_sbuf, 8192); + sbuf_reset(&hp->key_sbuf, 32); + hp->hls_encrypted = 0; return 0; } } @@ -221,7 +263,8 @@ iptv_http_data { http_priv_t *hp = hc->hc_aux; iptv_mux_t *im; - int pause = 0, rem; + int pause = 0, off, rem; + uint8_t tsbuf[188]; if (hp == NULL || hp->im == NULL || hc->hc_code != HTTP_STATUS_OK) return 0; @@ -232,41 +275,62 @@ iptv_http_data sbuf_append(&hp->m3u_sbuf, buf, len); return 0; } + if (hc->hc_data_complete == iptv_http_complete_key) { + sbuf_append(&hp->key_sbuf, buf, len); + return 0; + } + + pthread_mutex_lock(&iptv_lock); - if (hp->off == 0) { - tvhtrace("iptv", "hexdump"); - tvhlog_hexdump("iptv", buf, MAX(len, 4096)); - tvhtrace("iptv", "hexdump end"); + if (hp->hls_encrypted) { + off = im->mm_iptv_buffer.sb_ptr; + if (hp->hls_aes128.tmp_len + len >= AES_BLOCK_SIZE) { + if (hp->hls_aes128.tmp_len) { + sbuf_append(&im->mm_iptv_buffer, hp->hls_aes128.tmp, hp->hls_aes128.tmp_len); + rem = AES_BLOCK_SIZE - hp->hls_aes128.tmp_len; + hp->hls_aes128.tmp_len = 0; + sbuf_append(&im->mm_iptv_buffer, buf, rem); + len -= rem; + buf += rem; + } + rem = len % AES_BLOCK_SIZE; + sbuf_append(&im->mm_iptv_buffer, buf, len - rem); + buf += len - rem; + len = rem; + } + memcpy(hp->hls_aes128.tmp + hp->hls_aes128.tmp_len, buf, len); + hp->hls_aes128.tmp_len += len; + if (off == im->mm_iptv_buffer.sb_ptr) + return 0; + buf = im->mm_iptv_buffer.sb_data + im->mm_iptv_buffer.sb_ptr; + len = im->mm_iptv_buffer.sb_ptr - off; + assert((len % 16) == 0); + iptv_http_data_aes128(hp, &im->mm_iptv_buffer, off); + } else { + sbuf_append(&im->mm_iptv_buffer, buf, len); } + tsdebug_write((mpegts_mux_t *)im, buf, len); + hp->off += len; - if (hp->hls_url && hp->off < 2*188 && len >= 2*188) { + if (hp->hls_url && hp->off == 0 && len >= 2*188) { free(hp->hls_si); hp->hls_si = malloc(2*188); memcpy(hp->hls_si, buf, 2*188); } - pthread_mutex_lock(&iptv_lock); - if (dispatch_clock != hp->hls_last_si && hp->hls_si) { - /* do rounding to next MPEG-TS packet */ + /* do rounding to start of the last MPEG-TS packet */ rem = 188 - (hp->off % 188); - if (rem < 188) { - if (len < rem) - goto next; - sbuf_append(&im->mm_iptv_buffer, buf, rem); - buf += rem; - len -= rem; + if (im->mm_iptv_buffer.sb_ptr >= rem) { + im->mm_iptv_buffer.sb_ptr -= rem; + memcpy(tsbuf, im->mm_iptv_buffer.sb_data + im->mm_iptv_buffer.sb_ptr, rem); + sbuf_append(&im->mm_iptv_buffer, hp->hls_si, 2*188); + hp->hls_last_si = dispatch_clock; + sbuf_append(&im->mm_iptv_buffer, tsbuf, rem); hp->off += rem; } - sbuf_append(&im->mm_iptv_buffer, hp->hls_si, 2*188); - hp->hls_last_si = dispatch_clock; } -next: - hp->off += len; - sbuf_append(&im->mm_iptv_buffer, buf, len); - tsdebug_write((mpegts_mux_t *)im, buf, len); - if (len > 0) if (iptv_input_recv_packets(im, len) == 1) pause = hc->hc_pause = 1; @@ -284,14 +348,32 @@ next: /* * Complete data */ +static void +iptv_http_reconnect ( http_client_t *hc, const char *url ) +{ + url_t u; + int r; + + urlinit(&u); + if (!urlparse(url, &u)) { + hc->hc_keepalive = 0; + r = http_client_simple_reconnect(hc, &u, HTTP_VERSION_1_1); + if (r < 0) + tvherror("iptv", "cannot reopen http client: %d'", r); + } else { + tvherror("iptv", "m3u url invalid '%s'", url); + } + urlreset(&u); +} + static int iptv_http_complete ( http_client_t *hc ) { http_priv_t *hp = hc->hc_aux; + const char *s; char *url; htsmsg_t *m, *m2; - url_t u; int r; if (hp == NULL || hp->im == NULL) @@ -312,23 +394,46 @@ url: url = iptv_http_get_url(hp, m); if (hp->hls_m3u == m) m = NULL; + if (hp->hls_key) { + s = htsmsg_get_str(hp->hls_key, "METHOD"); + if (s == NULL || strcmp(s, "AES-128")) { + tvherror("iptv", "unknown crypto method '%s'", s); + goto end; + } + memset(&hp->hls_aes128.iv, 0, sizeof(hp->hls_aes128.iv)); + s = htsmsg_get_str(hp->hls_key, "IV"); + if (s != NULL) { + if (s[0] != '0' || s[1] != 'x' || strlen(s) != (AES_BLOCK_SIZE * 2) + 2) { + tvherror("iptv", "unknown IV type or length (%s)", s); + goto end; + } + hex2bin(hp->hls_aes128.iv, sizeof(hp->hls_aes128.iv), s + 2); + } + s = htsmsg_get_str(hp->hls_key, "URI"); + if (s == NULL) { + tvherror("iptv", "no URI in KEY attribute"); + goto end; + } + hp->hls_encrypted = 1; + if (strcmp(hp->hls_key_url ?: "", s)) { + free(hp->hls_key_url); + hp->hls_key_url = strdup(s); + free(hp->hls_url_after_key); + hp->hls_url_after_key = url; + url = strdup(s); + hc->hc_data_complete = iptv_http_complete_key; + sbuf_reset(&hp->key_sbuf, 32); + } + } tvhtrace("iptv", "m3u url: '%s'", url); if (url == NULL) { tvherror("iptv", "m3u contents parsing failed"); goto fin; } new_m3u: - urlinit(&u); - if (!urlparse(url, &u)) { - hc->hc_keepalive = 0; - r = http_client_simple_reconnect(hc, &u, HTTP_VERSION_1_1); - if (r < 0) - tvherror("iptv", "cannot reopen http client: %d'", r); - } else { - tvherror("iptv", "m3u url invalid '%s'", url); - } + iptv_http_reconnect(hc, url); +end: free(url); - urlreset(&u); fin: htsmsg_destroy(m); } else { @@ -352,6 +457,29 @@ fin: return 0; } +static int +iptv_http_complete_key + ( http_client_t *hc ) +{ + http_priv_t *hp = hc->hc_aux; + + if (hp == NULL) + return 0; + tvhtrace("iptv", "received key len %d", hp->key_sbuf.sb_ptr); + if (hp->key_sbuf.sb_ptr != AES_BLOCK_SIZE) { + tvherror("iptv", "AES-128 key wrong length (%d)\n", hp->key_sbuf.sb_ptr); + return 0; + } + AES_set_decrypt_key(hp->key_sbuf.sb_data, 128, &hp->hls_aes128.key); + hc->hc_data_complete = iptv_http_complete; + iptv_http_reconnect(hc, hp->hls_url_after_key); + free(hp->hls_url_after_key); + hp->hls_url_after_key = NULL; + htsmsg_destroy(hp->hls_key); + hp->hls_key = NULL; + return 0; +} + /* * Custom headers */ @@ -392,6 +520,7 @@ iptv_http_start hp->hc = hc; im->im_data = hp; sbuf_init(&hp->m3u_sbuf); + sbuf_init(&hp->key_sbuf); sbuf_init_fixed(&im->mm_iptv_buffer, IPTV_BUF_SIZE); http_client_register(hc); /* register to the HTTP thread */ r = http_client_simple(hc, u); @@ -420,8 +549,12 @@ iptv_http_stop pthread_mutex_lock(&iptv_lock); im->im_data = NULL; sbuf_free(&hp->m3u_sbuf); + sbuf_free(&hp->key_sbuf); htsmsg_destroy(hp->hls_m3u); + htsmsg_destroy(hp->hls_key); free(hp->hls_url); + free(hp->hls_url_after_key); + free(hp->hls_key_url); free(hp->hls_si); free(hp->host_url); free(hp);