Browse Source

fetch actual decryption key data from server

Gil Pedersen 12 years ago
parent
commit
3fc2e43b06
2 changed files with 52 additions and 15 deletions
  1. 51 14
      bin/hlsdump
  2. 1 1
      package.json

+ 51 - 14
bin/hlsdump

@@ -1,7 +1,5 @@
 #!/usr/bin/env node
 
-"use strict";
-
 var hlsdump = require('commander');
 hlsdump.version('0.0.0')
    .usage('[options] <url>')
@@ -33,10 +31,12 @@ hlsdump.version('0.0.0')
 var util = require('util'),
     url = require('url'),
     fs = require('fs'),
-    http = require('http');
+    http = require('http'),
+    crypto = require('crypto');
 
 var streamprocess = require('streamprocess'),
-    oncemore = require('oncemore');
+    oncemore = require('oncemore'),
+    uristream = require('uristream');
 var reader = require('../lib/reader'),
     tssmooth = require('../lib/tssmooth'),
     tsblast = require('../lib/tsblast');
@@ -59,6 +59,7 @@ var r = reader(src, {highWaterMark:(hlsdump.concurrent || 1) - 1, fullStream:hls
 
 var totalDuration = 0, currentSegment = -1;
 var reading = false;
+var keyCache = {};
 
 streamprocess(r, function (obj, done) {
   var meta = obj.meta;
@@ -76,7 +77,6 @@ streamprocess(r, function (obj, done) {
 
   reading = true;
   currentSegment = obj.seq;
-  stream.pipe(buffer, { end: false });
   if (size === -1 || !hooked) {
     size = 0;
     obj.stream.on('data', function(chunk) {
@@ -86,16 +86,53 @@ streamprocess(r, function (obj, done) {
     });
   }
 
-  stream.once('end', 'error', function(err) {
-    reading = false;
-    console.error('segment done at '+totalDuration.toFixed(0)+' seconds, avg bitrate (kbps):', (size / (duration * 1024/8)).toFixed(1));
-    if (err) {
-      stats.meter('streamErrors').mark();
-      console.error('stream error', err.stack || err);
+  var keyData = r.index.keyForSeqNo(obj.seq);
+  if (keyData && keyData.method === 'AES-128' && keyData.uri && keyData.uri.length > 2) {
+    fetchKey(function(err, key) {
+      if (err) {
+        console.error('key fetch failed:', err);
+        return pushBuffer(stream);
+      }
+      var iv = new Buffer(keyData.iv.slice(-32), 'hex');
+      var decrypt = crypto.createDecipheriv('aes-128-cbc', key, iv);
+
+      stream.on('error', function(err) {
+        decrypt.emit('error', err);
+      });
+      pushBuffer(oncemore(stream.pipe(decrypt)));
+    });
+
+    function fetchKey(cb) {
+      var uri = url.resolve(r.url, keyData.uri.slice(1,-1));
+      if (keyCache[uri]) return cb(null, keyCache[uri]);
+
+      var key = new Buffer(0);
+      oncemore(uristream(uri, { whitelist:['http', 'https', 'data'], timeout: 10*1000 }))
+        .on('data', function(chunk) {
+          key = Buffer.concat([key, chunk]);
+        })
+        .once('error', 'end', function(err) {
+          keyCache[uri] = key;
+          return cb(err, key);
+        });
     }
-    hook(buffer);
-    done();
-  });
+  } else {
+    pushBuffer(stream);
+  }
+
+  function pushBuffer(stream) {
+    stream.pipe(buffer, { end: false });
+    stream.once('end', 'error', function(err) {
+      reading = false;
+      console.error('segment done at '+totalDuration.toFixed(0)+' seconds, avg bitrate (kbps):', (size / (duration * 1024/8)).toFixed(1));
+      if (err) {
+        stats.meter('streamErrors').mark();
+        console.error('stream error', err.stack || err);
+      }
+      hook(buffer);
+      done();
+    });
+  }
 });
 
 r.once('index', function() {

+ 1 - 1
package.json

@@ -35,7 +35,7 @@
     "measured": "~0.1.3",
     "streamprocess": "0.0.1",
     "oncemore": "~0.1.0",
-    "m3u8parse": "~0.1.0",
+    "m3u8parse": "~0.1.5",
     "mime": "~1.2.11",
     "uristream": "~0.1.0"
   },