Browse code

avformat/hlsenc: added HLS encryption

Added HLS encryption with -hls_key_info_file <key_info_file> option. The
first line of key_info_file specifies the key URI written to the
playlist. The key URL is used to access the encryption key during
playback. The second line specifies the path to the key file used to
obtain the key during the encryption process. The key file is read as a
single packed array of 16 octets in binary format. The optional third
line specifies the initialization vector (IV) as a hexadecimal string to
be used instead of the segment sequence number (default) for encryption.
Changes to key_info_file will result in segment encryption with the new
key/IV and an entry in the playlist for the new key URI/IV.

Key info file format:
<key URI>
<key file path>
<IV> (optional)

Example key URIs:
http://server/file.key
/path/to/file.key
file.key

Example key file paths:
file.key
/path/to/file.key

Example IV:
0123456789ABCDEF0123456789ABCDEF

Example:
ffmpeg -f lavfi -i testsrc -c:v h264 -hls_key_info_file file.keyinfo
foo.m3u8

file.keyinfo:
http://server/file.key
/path/to/file.key
0123456789ABCDEF0123456789ABCDEF

Example shell script:
BASE_URL=${1:-'.'}
openssl rand 16 > file.key
echo $BASE_URL/file.key > file.keyinfo
echo file.key >> file.keyinfo
echo $(openssl rand -hex 16) >> file.keyinfo
ffmpeg -f lavfi -re -i testsrc -c:v h264 -hls_flags delete_segments \
-hls_key_info_file file.keyinfo out.m3u8
--

Signed-off-by: Christian Suloway <csuloway@globaleagleent.com>
Signed-off-by: Dan Dennedy <dan@dennedy.org>
Signed-off-by: Michael Niedermayer <michaelni@gmx.at>

Christian Suloway authored on 2015/06/16 03:58:07
Showing 2 changed files
... ...
@@ -263,6 +263,62 @@ ffmpeg in.nut -hls_segment_filename 'file%03d.ts' out.m3u8
263 263
 This example will produce the playlist, @file{out.m3u8}, and segment files:
264 264
 @file{file000.ts}, @file{file001.ts}, @file{file002.ts}, etc.
265 265
 
266
+@item hls_key_info_file @var{key_info_file}
267
+Use the information in @var{key_info_file} for segment encryption. The first
268
+line of @var{key_info_file} specifies the key URI written to the playlist. The
269
+key URL is used to access the encryption key during playback. The second line
270
+specifies the path to the key file used to obtain the key during the encryption
271
+process. The key file is read as a single packed array of 16 octets in binary
272
+format. The optional third line specifies the initialization vector (IV) as a
273
+hexadecimal string to be used instead of the segment sequence number (default)
274
+for encryption. Changes to @var{key_info_file} will result in segment
275
+encryption with the new key/IV and an entry in the playlist for the new key
276
+URI/IV.
277
+
278
+Key info file format:
279
+@example
280
+@var{key URI}
281
+@var{key file path}
282
+@var{IV} (optional)
283
+@end example
284
+
285
+Example key URIs:
286
+@example
287
+http://server/file.key
288
+/path/to/file.key
289
+file.key
290
+@end example
291
+
292
+Example key file paths:
293
+@example
294
+file.key
295
+/path/to/file.key
296
+@end example
297
+
298
+Example IV:
299
+@example
300
+0123456789ABCDEF0123456789ABCDEF
301
+@end example
302
+
303
+Key info file example:
304
+@example
305
+http://server/file.key
306
+/path/to/file.key
307
+0123456789ABCDEF0123456789ABCDEF
308
+@end example
309
+
310
+Example shell script:
311
+@example
312
+#!/bin/sh
313
+BASE_URL=${1:-'.'}
314
+openssl rand 16 > file.key
315
+echo $BASE_URL/file.key > file.keyinfo
316
+echo file.key >> file.keyinfo
317
+echo $(openssl rand -hex 16) >> file.keyinfo
318
+ffmpeg -f lavfi -re -i testsrc -c:v h264 -hls_flags delete_segments \
319
+  -hls_key_info_file file.keyinfo out.m3u8
320
+@end example
321
+
266 322
 @item hls_flags single_file
267 323
 If this flag is set, the muxer will store all segments in a single MPEG-TS
268 324
 file, and will use byte ranges in the playlist. HLS playlists generated with
... ...
@@ -37,12 +37,18 @@
37 37
 #include "internal.h"
38 38
 #include "os_support.h"
39 39
 
40
+#define KEYSIZE 16
41
+#define LINE_BUFFER_SIZE 1024
42
+
40 43
 typedef struct HLSSegment {
41 44
     char filename[1024];
42 45
     double duration; /* in seconds */
43 46
     int64_t pos;
44 47
     int64_t size;
45 48
 
49
+    char key_uri[LINE_BUFFER_SIZE + 1];
50
+    char iv_string[KEYSIZE*2 + 1];
51
+
46 52
     struct HLSSegment *next;
47 53
 } HLSSegment;
48 54
 
... ...
@@ -89,6 +95,12 @@ typedef struct HLSContext {
89 89
     char *baseurl;
90 90
     char *format_options_str;
91 91
     AVDictionary *format_options;
92
+
93
+    char *key_info_file;
94
+    char key_file[LINE_BUFFER_SIZE + 1];
95
+    char key_uri[LINE_BUFFER_SIZE + 1];
96
+    char key_string[KEYSIZE*2 + 1];
97
+    char iv_string[KEYSIZE*2 + 1];
92 98
 } HLSContext;
93 99
 
94 100
 static int hls_delete_old_segments(HLSContext *hls) {
... ...
@@ -156,6 +168,60 @@ fail:
156 156
     return ret;
157 157
 }
158 158
 
159
+static int hls_encryption_start(AVFormatContext *s)
160
+{
161
+    HLSContext *hls = s->priv_data;
162
+    int ret;
163
+    AVIOContext *pb;
164
+    uint8_t key[KEYSIZE];
165
+
166
+    if ((ret = avio_open2(&pb, hls->key_info_file, AVIO_FLAG_READ,
167
+                           &s->interrupt_callback, NULL)) < 0) {
168
+        av_log(hls, AV_LOG_ERROR,
169
+                "error opening key info file %s\n", hls->key_info_file);
170
+        return ret;
171
+    }
172
+
173
+    ff_get_line(pb, hls->key_uri, sizeof(hls->key_uri));
174
+    hls->key_uri[strcspn(hls->key_uri, "\r\n")] = '\0';
175
+
176
+    ff_get_line(pb, hls->key_file, sizeof(hls->key_file));
177
+    hls->key_file[strcspn(hls->key_file, "\r\n")] = '\0';
178
+
179
+    ff_get_line(pb, hls->iv_string, sizeof(hls->iv_string));
180
+    hls->iv_string[strcspn(hls->iv_string, "\r\n")] = '\0';
181
+
182
+    avio_close(pb);
183
+
184
+    if (!*hls->key_uri) {
185
+        av_log(hls, AV_LOG_ERROR, "no key URI specified in key info file\n");
186
+        return AVERROR(EINVAL);
187
+    }
188
+
189
+    if (!*hls->key_file) {
190
+        av_log(hls, AV_LOG_ERROR, "no key file specified in key info file\n");
191
+        return AVERROR(EINVAL);
192
+    }
193
+
194
+    if ((ret = avio_open2(&pb, hls->key_file, AVIO_FLAG_READ,
195
+                           &s->interrupt_callback, NULL)) < 0) {
196
+        av_log(hls, AV_LOG_ERROR, "error opening key file %s\n", hls->key_file);
197
+        return ret;
198
+    }
199
+
200
+    ret = avio_read(pb, key, sizeof(key));
201
+    avio_close(pb);
202
+    if (ret != sizeof(key)) {
203
+        av_log(hls, AV_LOG_ERROR, "error reading key file %s\n", hls->key_file);
204
+        if (ret >= 0 || ret == AVERROR_EOF)
205
+            ret = AVERROR(EINVAL);
206
+        return ret;
207
+    }
208
+    ff_data_to_hex(hls->key_string, key, sizeof(key), 0);
209
+
210
+    return 0;
211
+}
212
+
159 213
 static int hls_mux_init(AVFormatContext *s)
160 214
 {
161 215
     HLSContext *hls = s->priv_data;
... ...
@@ -202,6 +268,11 @@ static int hls_append_segment(HLSContext *hls, double duration, int64_t pos,
202 202
     en->size     = size;
203 203
     en->next     = NULL;
204 204
 
205
+    if (hls->key_info_file) {
206
+        av_strlcpy(en->key_uri, hls->key_uri, sizeof(en->key_uri));
207
+        av_strlcpy(en->iv_string, hls->iv_string, sizeof(en->iv_string));
208
+    }
209
+
205 210
     if (!hls->segments)
206 211
         hls->segments = en;
207 212
     else
... ...
@@ -239,6 +310,10 @@ static void hls_free_segments(HLSSegment *p)
239 239
     }
240 240
 }
241 241
 
242
+static void print_encryption_tag(HLSContext *hls, HLSSegment *en)
243
+{
244
+}
245
+
242 246
 static int hls_window(AVFormatContext *s, int last)
243 247
 {
244 248
     HLSContext *hls = s->priv_data;
... ...
@@ -252,6 +327,8 @@ static int hls_window(AVFormatContext *s, int last)
252 252
     const char *proto = avio_find_protocol_name(s->filename);
253 253
     int use_rename = proto && !strcmp(proto, "file");
254 254
     static unsigned warned_non_file;
255
+    char *key_uri = NULL;
256
+    char *iv_string = NULL;
255 257
 
256 258
     if (!use_rename && !warned_non_file++)
257 259
         av_log(s, AV_LOG_ERROR, "Cannot use rename on non file protocol, this may lead to races and temporarly partial files\n");
... ...
@@ -282,6 +359,16 @@ static int hls_window(AVFormatContext *s, int last)
282 282
         hls->discontinuity_set = 1;
283 283
     }
284 284
     for (en = hls->segments; en; en = en->next) {
285
+        if (hls->key_info_file && (!key_uri || strcmp(en->key_uri, key_uri) ||
286
+                                    av_strcasecmp(en->iv_string, iv_string))) {
287
+            avio_printf(out, "#EXT-X-KEY:METHOD=AES-128,URI=\"%s\"", en->key_uri);
288
+            if (*en->iv_string)
289
+                avio_printf(out, ",IV=0x%s", en->iv_string);
290
+            avio_printf(out, "\n");
291
+            key_uri = en->key_uri;
292
+            iv_string = en->iv_string;
293
+        }
294
+
285 295
         if (hls->flags & HLS_ROUND_DURATIONS)
286 296
             avio_printf(out, "#EXTINF:%d,\n",  (int)round(en->duration));
287 297
         else
... ...
@@ -308,6 +395,8 @@ static int hls_start(AVFormatContext *s)
308 308
 {
309 309
     HLSContext *c = s->priv_data;
310 310
     AVFormatContext *oc = c->avf;
311
+    AVDictionary *options = NULL;
312
+    char *filename, iv_string[KEYSIZE*2 + 1];
311 313
     int err = 0;
312 314
 
313 315
     if (c->flags & HLS_SINGLE_FILE)
... ...
@@ -321,9 +410,33 @@ static int hls_start(AVFormatContext *s)
321 321
         }
322 322
     c->number++;
323 323
 
324
-    if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
324
+    if (c->key_info_file) {
325
+        if ((err = hls_encryption_start(s)) < 0)
326
+            return err;
327
+        if ((err = av_dict_set(&options, "encryption_key", c->key_string, 0))
328
+                < 0)
329
+            return err;
330
+        err = av_strlcpy(iv_string, c->iv_string, sizeof(iv_string));
331
+        if (!err)
332
+            snprintf(iv_string, sizeof(iv_string), "%032"PRIx64, c->sequence);
333
+        if ((err = av_dict_set(&options, "encryption_iv", iv_string, 0)) < 0)
334
+            return err;
335
+
336
+        filename = av_asprintf("crypto:%s", oc->filename);
337
+        if (!filename) {
338
+            av_dict_free(&options);
339
+            return AVERROR(ENOMEM);
340
+        }
341
+        err = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE,
342
+                         &s->interrupt_callback, &options);
343
+        av_free(filename);
344
+        av_dict_free(&options);
345
+        if (err < 0)
346
+            return err;
347
+    } else
348
+        if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
325 349
                           &s->interrupt_callback, NULL)) < 0)
326
-        return err;
350
+            return err;
327 351
 
328 352
     if (oc->oformat->priv_class && oc->priv_data)
329 353
         av_opt_set(oc->priv_data, "mpegts_flags", "resend_headers", 0);
... ...
@@ -520,6 +633,7 @@ static const AVOption options[] = {
520 520
     {"hls_allow_cache", "explicitly set whether the client MAY (1) or MUST NOT (0) cache media segments", OFFSET(allowcache), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, E},
521 521
     {"hls_base_url",  "url to prepend to each playlist entry",   OFFSET(baseurl), AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E},
522 522
     {"hls_segment_filename", "filename template for segment files", OFFSET(segment_filename),   AV_OPT_TYPE_STRING, {.str = NULL},            0,       0,         E},
523
+    {"hls_key_info_file",    "file with key URI and key file path", OFFSET(key_info_file),      AV_OPT_TYPE_STRING, {.str = NULL},            0,       0,         E},
523 524
     {"hls_flags",     "set flags affecting HLS playlist and media file generation", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = 0 }, 0, UINT_MAX, E, "flags"},
524 525
     {"single_file",   "generate a single media file indexed with byte ranges", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SINGLE_FILE }, 0, UINT_MAX,   E, "flags"},
525 526
     {"delete_segments", "delete segment files that are no longer part of the playlist", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_DELETE_SEGMENTS }, 0, UINT_MAX,   E, "flags"},