]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
IPTV: HLS - add AES-128 decryption support
authorJaroslav Kysela <perex@perex.cz>
Fri, 20 Nov 2015 13:37:04 +0000 (14:37 +0100)
committerJaroslav Kysela <perex@perex.cz>
Fri, 20 Nov 2015 13:37:04 +0000 (14:37 +0100)
src/input/mpegts/iptv/iptv_http.c

index b42e40bc009f1e68c8e0d8d34fcb76e93bb0cfd6..cadbdf7e14d671764900372180e6fa8aefb0d535 100644 (file)
@@ -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
 #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);