ec01a09c by brandonocasey

browserify-p3: decrypter

convert decrypter module to es6/node/browserify
stub old functionalify to get all tests working
updated scripts to keep builds and tests working
1 parent a8daa7f7
...@@ -47,13 +47,11 @@ ...@@ -47,13 +47,11 @@
47 47
48 <script src="/node_modules/video.js/dist/video.js"></script> 48 <script src="/node_modules/video.js/dist/video.js"></script>
49 <script src="/node_modules/videojs-contrib-media-sources/dist/videojs-media-sources.js"></script> 49 <script src="/node_modules/videojs-contrib-media-sources/dist/videojs-media-sources.js"></script>
50 <script src="/node_modules/pkcs7/dist/pkcs7.unpad.js"></script>
51 <script src="/src/videojs-contrib-hls.js"></script> 50 <script src="/src/videojs-contrib-hls.js"></script>
52 <script src="/src/xhr.js"></script> 51 <script src="/src/xhr.js"></script>
53 <script src="/dist/videojs-contrib-hls.js"></script> 52 <script src="/dist/videojs-contrib-hls.js"></script>
54 <script src="/src/playlist.js"></script> 53 <script src="/src/playlist.js"></script>
55 <script src="/src/playlist-loader.js"></script> 54 <script src="/src/playlist-loader.js"></script>
56 <script src="/src/decrypter.js"></script>
57 <script src="/src/bin-utils.js"></script> 55 <script src="/src/bin-utils.js"></script>
58 <script> 56 <script>
59 (function(window, videojs) { 57 (function(window, videojs) {
......
...@@ -2,7 +2,7 @@ var browserify = require('browserify'); ...@@ -2,7 +2,7 @@ var browserify = require('browserify');
2 var fs = require('fs'); 2 var fs = require('fs');
3 var glob = require('glob'); 3 var glob = require('glob');
4 4
5 glob('test/{m3u8,stub}.test.js', function(err, files) { 5 glob('test/{decryper,m3u8,stub}.test.js', function(err, files) {
6 browserify(files) 6 browserify(files)
7 .transform('babelify') 7 .transform('babelify')
8 .bundle() 8 .bundle()
......
...@@ -3,7 +3,7 @@ var fs = require('fs'); ...@@ -3,7 +3,7 @@ var fs = require('fs');
3 var glob = require('glob'); 3 var glob = require('glob');
4 var watchify = require('watchify'); 4 var watchify = require('watchify');
5 5
6 glob('test/{m3u8,stub}.test.js', function(err, files) { 6 glob('test/{decrypter,m3u8,stub}.test.js', function(err, files) {
7 var b = browserify(files, { 7 var b = browserify(files, {
8 cache: {}, 8 cache: {},
9 packageCache: {}, 9 packageCache: {},
......
1 import Stream from './stream';
2 import {unpad} from 'pkcs7';
3
4 /**
5 * Convert network-order (big-endian) bytes into their little-endian
6 * representation.
7 */
8 const ntoh = function(word) {
9 return (word << 24) |
10 ((word & 0xff00) << 8) |
11 ((word & 0xff0000) >> 8) |
12 (word >>> 24);
13 };
14
1 /* 15 /*
2 * 16 *
3 * This file contains an adaptation of the AES decryption algorithm 17 * This file contains an adaptation of the AES decryption algorithm
...@@ -35,21 +49,6 @@ ...@@ -35,21 +49,6 @@
35 * are those of the authors and should not be interpreted as representing 49 * are those of the authors and should not be interpreted as representing
36 * official policies, either expressed or implied, of the authors. 50 * official policies, either expressed or implied, of the authors.
37 */ 51 */
38 (function(window, videojs, unpad) {
39 'use strict';
40
41 var AES, AsyncStream, Decrypter, decrypt, ntoh;
42
43 /**
44 * Convert network-order (big-endian) bytes into their little-endian
45 * representation.
46 */
47 ntoh = function(word) {
48 return (word << 24) |
49 ((word & 0xff00) << 8) |
50 ((word & 0xff0000) >> 8) |
51 (word >>> 24);
52 };
53 52
54 /** 53 /**
55 * Schedule out an AES key for both encryption and decryption. This 54 * Schedule out an AES key for both encryption and decryption. This
...@@ -58,108 +57,74 @@ ntoh = function(word) { ...@@ -58,108 +57,74 @@ ntoh = function(word) {
58 * @constructor 57 * @constructor
59 * @param key {Array} The key as an array of 4, 6 or 8 words. 58 * @param key {Array} The key as an array of 4, 6 or 8 words.
60 */ 59 */
61 AES = function (key) { 60 class AES {
62 this._precompute(); 61 constructor(key) {
63 62 /**
64 var i, j, tmp, 63 * The expanded S-box and inverse S-box tables. These will be computed
65 encKey, decKey, 64 * on the client so that we don't have to send them down the wire.
66 sbox = this._tables[0][4], decTable = this._tables[1], 65 *
67 keyLen = key.length, rcon = 1; 66 * There are two tables, _tables[0] is for encryption and
68 67 * _tables[1] is for decryption.
69 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { 68 *
70 throw new Error("Invalid aes key size"); 69 * The first 4 sub-tables are the expanded S-box with MixColumns. The
71 } 70 * last (_tables[01][4]) is the S-box itself.
72 71 *
73 encKey = key.slice(0); 72 * @private
74 decKey = []; 73 */
75 this._key = [encKey, decKey]; 74 this._tables = [[[], [], [], [], []], [[], [], [], [], []]];
76 75 this._precompute();
77 // schedule encryption keys 76 let i;
78 for (i = keyLen; i < 4 * keyLen + 28; i++) { 77 let j;
79 tmp = encKey[i-1]; 78 let tmp;
80 79 let encKey;
81 // apply sbox 80 let decKey;
82 if (i%keyLen === 0 || (keyLen === 8 && i%keyLen === 4)) { 81 let sbox = this._tables[0][4];
83 tmp = sbox[tmp>>>24]<<24 ^ sbox[tmp>>16&255]<<16 ^ sbox[tmp>>8&255]<<8 ^ sbox[tmp&255]; 82 let decTable = this._tables[1];
83 let keyLen = key.length;
84 let rcon = 1;
85
86 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
87 throw new Error('Invalid aes key size');
88 }
84 89
85 // shift rows and add rcon 90 encKey = key.slice(0);
86 if (i%keyLen === 0) { 91 decKey = [];
87 tmp = tmp<<8 ^ tmp>>>24 ^ rcon<<24; 92 this._key = [encKey, decKey];
88 rcon = rcon<<1 ^ (rcon>>7)*283; 93
94 // schedule encryption keys
95 for (i = keyLen; i < 4 * keyLen + 28; i++) {
96 tmp = encKey[i - 1];
97
98 // apply sbox
99 if (i % keyLen === 0 || (keyLen === 8 && i % keyLen === 4)) {
100 tmp = sbox[tmp >>> 24] << 24 ^
101 sbox[tmp >> 16 & 255] << 16 ^
102 sbox[tmp >> 8 & 255] << 8 ^
103 sbox[tmp & 255];
104
105 // shift rows and add rcon
106 if (i % keyLen === 0) {
107 tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
108 rcon = rcon << 1 ^ (rcon >> 7) * 283;
109 }
89 } 110 }
90 }
91 111
92 encKey[i] = encKey[i-keyLen] ^ tmp; 112 encKey[i] = encKey[i - keyLen] ^ tmp;
93 } 113 }
94 114
95 // schedule decryption keys 115 // schedule decryption keys
96 for (j = 0; i; j++, i--) { 116 for (j = 0; i; j++, i--) {
97 tmp = encKey[j&3 ? i : i - 4]; 117 tmp = encKey[j & 3 ? i : i - 4];
98 if (i<=4 || j<4) { 118 if (i <= 4 || j < 4) {
99 decKey[j] = tmp; 119 decKey[j] = tmp;
100 } else { 120 } else {
101 decKey[j] = decTable[0][sbox[tmp>>>24 ]] ^ 121 decKey[j] = decTable[0][sbox[tmp >>> 24 ]] ^
102 decTable[1][sbox[tmp>>16 & 255]] ^ 122 decTable[1][sbox[tmp >> 16 & 255]] ^
103 decTable[2][sbox[tmp>>8 & 255]] ^ 123 decTable[2][sbox[tmp >> 8 & 255]] ^
104 decTable[3][sbox[tmp & 255]]; 124 decTable[3][sbox[tmp & 255]];
125 }
105 } 126 }
106 } 127 }
107 };
108
109 AES.prototype = {
110 /**
111 * The expanded S-box and inverse S-box tables. These will be computed
112 * on the client so that we don't have to send them down the wire.
113 *
114 * There are two tables, _tables[0] is for encryption and
115 * _tables[1] is for decryption.
116 *
117 * The first 4 sub-tables are the expanded S-box with MixColumns. The
118 * last (_tables[01][4]) is the S-box itself.
119 *
120 * @private
121 */
122 _tables: [[[],[],[],[],[]],[[],[],[],[],[]]],
123
124 /**
125 * Expand the S-box tables.
126 *
127 * @private
128 */
129 _precompute: function () {
130 var encTable = this._tables[0], decTable = this._tables[1],
131 sbox = encTable[4], sboxInv = decTable[4],
132 i, x, xInv, d=[], th=[], x2, x4, x8, s, tEnc, tDec;
133
134 // Compute double and third tables
135 for (i = 0; i < 256; i++) {
136 th[( d[i] = i<<1 ^ (i>>7)*283 )^i]=i;
137 }
138
139 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
140 // Compute sbox
141 s = xInv ^ xInv<<1 ^ xInv<<2 ^ xInv<<3 ^ xInv<<4;
142 s = s>>8 ^ s&255 ^ 99;
143 sbox[x] = s;
144 sboxInv[s] = x;
145
146 // Compute MixColumns
147 x8 = d[x4 = d[x2 = d[x]]];
148 tDec = x8*0x1010101 ^ x4*0x10001 ^ x2*0x101 ^ x*0x1010100;
149 tEnc = d[s]*0x101 ^ s*0x1010100;
150
151 for (i = 0; i < 4; i++) {
152 encTable[i][x] = tEnc = tEnc<<24 ^ tEnc>>>8;
153 decTable[i][s] = tDec = tDec<<24 ^ tDec>>>8;
154 }
155 }
156
157 // Compactify. Considerable speedup on Firefox.
158 for (i = 0; i < 5; i++) {
159 encTable[i] = encTable[i].slice(0);
160 decTable[i] = decTable[i].slice(0);
161 }
162 },
163 128
164 /** 129 /**
165 * Decrypt 16 bytes, specified as four 32-bit words. 130 * Decrypt 16 bytes, specified as four 32-bit words.
...@@ -173,50 +138,122 @@ AES.prototype = { ...@@ -173,50 +138,122 @@ AES.prototype = {
173 * writing results 138 * writing results
174 * @return {Array} The plaintext. 139 * @return {Array} The plaintext.
175 */ 140 */
176 decrypt:function (encrypted0, encrypted1, encrypted2, encrypted3, out, offset) { 141 decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
177 var key = this._key[1], 142 let key = this._key[1];
178 // state variables a,b,c,d are loaded with pre-whitened data 143 // state variables a,b,c,d are loaded with pre-whitened data
179 a = encrypted0 ^ key[0], 144 let a = encrypted0 ^ key[0];
180 b = encrypted3 ^ key[1], 145 let b = encrypted3 ^ key[1];
181 c = encrypted2 ^ key[2], 146 let c = encrypted2 ^ key[2];
182 d = encrypted1 ^ key[3], 147 let d = encrypted1 ^ key[3];
183 a2, b2, c2, 148 let a2;
184 149 let b2;
185 nInnerRounds = key.length / 4 - 2, // key.length === 2 ? 150 let c2;
186 i, 151
187 kIndex = 4, 152 // key.length === 2 ?
188 table = this._tables[1], 153 let nInnerRounds = key.length / 4 - 2;
189 154 let i;
190 // load up the tables 155 let kIndex = 4;
191 table0 = table[0], 156 let table = this._tables[1];
192 table1 = table[1], 157
193 table2 = table[2], 158 // load up the tables
194 table3 = table[3], 159 let table0 = table[0];
195 sbox = table[4]; 160 let table1 = table[1];
161 let table2 = table[2];
162 let table3 = table[3];
163 let sbox = table[4];
196 164
197 // Inner rounds. Cribbed from OpenSSL. 165 // Inner rounds. Cribbed from OpenSSL.
198 for (i = 0; i < nInnerRounds; i++) { 166 for (i = 0; i < nInnerRounds; i++) {
199 a2 = table0[a>>>24] ^ table1[b>>16 & 255] ^ table2[c>>8 & 255] ^ table3[d & 255] ^ key[kIndex]; 167 a2 = table0[a >>> 24] ^
200 b2 = table0[b>>>24] ^ table1[c>>16 & 255] ^ table2[d>>8 & 255] ^ table3[a & 255] ^ key[kIndex + 1]; 168 table1[b >> 16 & 255] ^
201 c2 = table0[c>>>24] ^ table1[d>>16 & 255] ^ table2[a>>8 & 255] ^ table3[b & 255] ^ key[kIndex + 2]; 169 table2[c >> 8 & 255] ^
202 d = table0[d>>>24] ^ table1[a>>16 & 255] ^ table2[b>>8 & 255] ^ table3[c & 255] ^ key[kIndex + 3]; 170 table3[d & 255] ^
171 key[kIndex];
172 b2 = table0[b >>> 24] ^
173 table1[c >> 16 & 255] ^
174 table2[d >> 8 & 255] ^
175 table3[a & 255] ^
176 key[kIndex + 1];
177 c2 = table0[c >>> 24] ^
178 table1[d >> 16 & 255] ^
179 table2[a >> 8 & 255] ^
180 table3[b & 255] ^
181 key[kIndex + 2];
182 d = table0[d >>> 24] ^
183 table1[a >> 16 & 255] ^
184 table2[b >> 8 & 255] ^
185 table3[c & 255] ^
186 key[kIndex + 3];
203 kIndex += 4; 187 kIndex += 4;
204 a=a2; b=b2; c=c2; 188 a = a2; b = b2; c = c2;
205 } 189 }
206 190
207 // Last round. 191 // Last round.
208 for (i = 0; i < 4; i++) { 192 for (i = 0; i < 4; i++) {
209 out[(3 & -i) + offset] = 193 out[(3 & -i) + offset] =
210 sbox[a>>>24 ]<<24 ^ 194 sbox[a >>> 24] << 24 ^
211 sbox[b>>16 & 255]<<16 ^ 195 sbox[b >> 16 & 255] << 16 ^
212 sbox[c>>8 & 255]<<8 ^ 196 sbox[c >> 8 & 255] << 8 ^
213 sbox[d & 255] ^ 197 sbox[d & 255] ^
214 key[kIndex++]; 198 key[kIndex++];
215 a2=a; a=b; b=c; c=d; d=a2; 199 a2 = a; a = b; b = c; c = d; d = a2;
216 } 200 }
217 } 201 }
218 };
219 202
203 /**
204 * Expand the S-box tables.
205 *
206 * @private
207 */
208 _precompute() {
209 let encTable = this._tables[0];
210 let decTable = this._tables[1];
211 let sbox = encTable[4];
212 let sboxInv = decTable[4];
213 let i;
214 let x;
215 let xInv;
216 let d = [];
217 let th = [];
218 let x2;
219 let x4;
220 let x8;
221 let s;
222 let tEnc;
223 let tDec;
224
225 // Compute double and third tables
226 for (i = 0; i < 256; i++) {
227 th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
228 }
229
230 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
231 // Compute sbox
232 s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
233 s = s >> 8 ^ s & 255 ^ 99;
234 sbox[x] = s;
235 sboxInv[s] = x;
236
237 // Compute MixColumns
238 x8 = d[x4 = d[x2 = d[x]]];
239 tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
240 tEnc = d[s] * 0x101 ^ s * 0x1010100;
241
242 for (i = 0; i < 4; i++) {
243 encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
244 decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
245 }
246 }
247
248 // Compactify. Considerable speedup on Firefox.
249 for (i = 0; i < 5; i++) {
250 encTable[i] = encTable[i].slice(0);
251 decTable[i] = decTable[i].slice(0);
252 }
253 }
254 }
255
256 /* eslint-disable max-len */
220 /** 257 /**
221 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. 258 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
222 * @param encrypted {Uint8Array} the encrypted bytes 259 * @param encrypted {Uint8Array} the encrypted bytes
...@@ -229,24 +266,34 @@ AES.prototype = { ...@@ -229,24 +266,34 @@ AES.prototype = {
229 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 266 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
230 * @see https://tools.ietf.org/html/rfc2315 267 * @see https://tools.ietf.org/html/rfc2315
231 */ 268 */
232 decrypt = function(encrypted, key, initVector) { 269 /* eslint-enable max-len */
233 var 270 export const decrypt = function(encrypted, key, initVector) {
234 // word-level access to the encrypted bytes 271 // word-level access to the encrypted bytes
235 encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2), 272 let encrypted32 = new Int32Array(
236 273 encrypted.buffer,
237 decipher = new AES(Array.prototype.slice.call(key)), 274 encrypted.byteOffset,
238 275 encrypted.byteLength >> 2
239 // byte and word-level access for the decrypted output 276 );
240 decrypted = new Uint8Array(encrypted.byteLength), 277
241 decrypted32 = new Int32Array(decrypted.buffer), 278 let decipher = new AES(Array.prototype.slice.call(key));
242 279
243 // temporary variables for working with the IV, encrypted, and 280 // byte and word-level access for the decrypted output
244 // decrypted data 281 let decrypted = new Uint8Array(encrypted.byteLength);
245 init0, init1, init2, init3, 282 let decrypted32 = new Int32Array(decrypted.buffer);
246 encrypted0, encrypted1, encrypted2, encrypted3, 283
247 284 // temporary variables for working with the IV, encrypted, and
248 // iteration variable 285 // decrypted data
249 wordIx; 286 let init0;
287 let init1;
288 let init2;
289 let init3;
290 let encrypted0;
291 let encrypted1;
292 let encrypted2;
293 let encrypted3;
294
295 // iteration variable
296 let wordIx;
250 297
251 // pull out the words of the IV to ensure we don't modify the 298 // pull out the words of the IV to ensure we don't modify the
252 // passed-in reference and easier access 299 // passed-in reference and easier access
...@@ -266,16 +313,18 @@ decrypt = function(encrypted, key, initVector) { ...@@ -266,16 +313,18 @@ decrypt = function(encrypted, key, initVector) {
266 encrypted3 = ntoh(encrypted32[wordIx + 3]); 313 encrypted3 = ntoh(encrypted32[wordIx + 3]);
267 314
268 // decrypt the block 315 // decrypt the block
269 decipher.decrypt(encrypted0, 316 decipher.decrypt(
270 encrypted1, 317 encrypted0,
271 encrypted2, 318 encrypted1,
272 encrypted3, 319 encrypted2,
273 decrypted32, 320 encrypted3,
274 wordIx); 321 decrypted32,
322 wordIx
323 );
275 324
276 // XOR with the IV, and restore network byte-order to obtain the 325 // XOR with the IV, and restore network byte-order to obtain the
277 // plaintext 326 // plaintext
278 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); 327 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
279 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); 328 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
280 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); 329 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
281 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); 330 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3);
...@@ -290,75 +339,82 @@ decrypt = function(encrypted, key, initVector) { ...@@ -290,75 +339,82 @@ decrypt = function(encrypted, key, initVector) {
290 return decrypted; 339 return decrypted;
291 }; 340 };
292 341
293 AsyncStream = function() { 342 export class AsyncStream extends Stream {
294 this.jobs = []; 343 constructor() {
295 this.delay = 1; 344 super(Stream);
296 this.timeout_ = null; 345 this.jobs = [];
297 }; 346 this.delay = 1;
298 AsyncStream.prototype = new videojs.Hls.Stream();
299 AsyncStream.prototype.processJob_ = function() {
300 this.jobs.shift()();
301 if (this.jobs.length) {
302 this.timeout_ = setTimeout(this.processJob_.bind(this),
303 this.delay);
304 } else {
305 this.timeout_ = null; 347 this.timeout_ = null;
306 } 348 }
307 }; 349 processJob_() {
308 AsyncStream.prototype.push = function(job) { 350 this.jobs.shift()();
309 this.jobs.push(job); 351 if (this.jobs.length) {
310 if (!this.timeout_) { 352 this.timeout_ = setTimeout(
311 this.timeout_ = setTimeout(this.processJob_.bind(this), 353 this.processJob_.bind(this),
312 this.delay); 354 this.delay
355 );
356 } else {
357 this.timeout_ = null;
358 }
313 } 359 }
314 }; 360 push(job) {
361 this.jobs.push(job);
362 if (!this.timeout_) {
363 this.timeout_ = setTimeout(
364 this.processJob_.bind(this),
365 this.delay
366 );
367 }
368 }
369 }
370
371 // the maximum number of bytes to process at one time
372 const decrypterStep = 4 * 8000;
315 373
316 Decrypter = function(encrypted, key, initVector, done) { 374 export class Decrypter extends Stream {
317 var 375 constructor(encrypted, key, initVector, done) {
318 step = Decrypter.STEP, 376 super(Stream);
319 encrypted32 = new Int32Array(encrypted.buffer), 377 let step = decrypterStep;
320 decrypted = new Uint8Array(encrypted.byteLength), 378 let encrypted32 = new Int32Array(encrypted.buffer);
321 i = 0; 379 let decrypted = new Uint8Array(encrypted.byteLength);
322 this.asyncStream_ = new AsyncStream(); 380 let i = 0;
323 381
324 // split up the encryption job and do the individual chunks asynchronously 382 this.asyncStream_ = new AsyncStream();
325 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), 383
326 key, 384 // split up the encryption job and do the individual chunks asynchronously
327 initVector,
328 decrypted));
329 for (i = step; i < encrypted32.length; i += step) {
330 initVector = new Uint32Array([
331 ntoh(encrypted32[i - 4]),
332 ntoh(encrypted32[i - 3]),
333 ntoh(encrypted32[i - 2]),
334 ntoh(encrypted32[i - 1])
335 ]);
336 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), 385 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step),
337 key, 386 key,
338 initVector, 387 initVector,
339 decrypted)); 388 decrypted));
389 for (i = step; i < encrypted32.length; i += step) {
390 initVector = new Uint32Array([ntoh(encrypted32[i - 4]),
391 ntoh(encrypted32[i - 3]),
392 ntoh(encrypted32[i - 2]),
393 ntoh(encrypted32[i - 1])]);
394 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step),
395 key,
396 initVector,
397 decrypted));
398 }
399 // invoke the done() callback when everything is finished
400 this.asyncStream_.push(function() {
401 // remove pkcs#7 padding from the decrypted bytes
402 done(null, unpad(decrypted));
403 });
340 } 404 }
341 // invoke the done() callback when everything is finished 405 decryptChunk_(encrypted, key, initVector, decrypted) {
342 this.asyncStream_.push(function() { 406 return function() {
343 // remove pkcs#7 padding from the decrypted bytes 407 let bytes = decrypt(encrypted, key, initVector);
344 done(null, unpad(decrypted)); 408
345 }); 409 decrypted.set(bytes, encrypted.byteOffset);
346 }; 410 };
347 Decrypter.prototype = new videojs.Hls.Stream(); 411 }
348 Decrypter.prototype.decryptChunk_ = function(encrypted, key, initVector, decrypted) { 412 }
349 return function() {
350 var bytes = decrypt(encrypted,
351 key,
352 initVector);
353 decrypted.set(bytes, encrypted.byteOffset);
354 };
355 };
356 // the maximum number of bytes to process at one time
357 Decrypter.STEP = 4 * 8000;
358 413
359 // exports 414 Decrypter.STEP = decrypterStep;
360 videojs.Hls.decrypt = decrypt;
361 videojs.Hls.Decrypter = Decrypter;
362 videojs.Hls.AsyncStream = AsyncStream;
363 415
364 })(window, window.videojs, window.pkcs7.unpad); 416 export default {
417 decrypt,
418 Decrypter,
419 AsyncStream
420 };
......
1 import m3u8 from './m3u8'; 1 import m3u8 from './m3u8';
2 import Stream from './stream'; 2 import Stream from './stream';
3 import videojs from 'video.js'; 3 import videojs from 'video.js';
4 import {Decrypter, decrypt, AsyncStream} from './decrypter';
4 5
5 if(typeof window.videojs.Hls === 'undefined') { 6 if(typeof window.videojs.Hls === 'undefined') {
6 videojs.Hls = {}; 7 videojs.Hls = {};
7 } 8 }
8 videojs.Hls.Stream = Stream; 9 videojs.Hls.Stream = Stream;
9 videojs.m3u8 = m3u8; 10 videojs.m3u8 = m3u8;
10 11 videojs.Hls.decrypt = decrypt;
12 videojs.Hls.Decrypter = Decrypter;
13 videojs.Hls.AsyncStream = AsyncStream;
......
1 (function(window, videojs, unpad, undefined) {
2 'use strict';
3 /*
4 ======== A Handy Little QUnit Reference ========
5 http://api.qunitjs.com/
6
7 Test methods:
8 module(name, {[setup][ ,teardown]})
9 test(name, callback)
10 expect(numberOfAssertions)
11 stop(increment)
12 start(decrement)
13 Test assertions:
14 ok(value, [message])
15 equal(actual, expected, [message])
16 notEqual(actual, expected, [message])
17 deepEqual(actual, expected, [message])
18 notDeepEqual(actual, expected, [message])
19 strictEqual(actual, expected, [message])
20 notStrictEqual(actual, expected, [message])
21 throws(block, [expected], [message])
22 */
23
24 // see docs/hlse.md for instructions on how test data was generated 1 // see docs/hlse.md for instructions on how test data was generated
2 import QUnit from 'qunit';
3 import {unpad} from 'pkcs7';
4 import sinon from 'sinon';
5 import {decrypt, Decrypter, AsyncStream} from '../src/decrypter';
25 6
26 var stringFromBytes = function(bytes) { 7 // see docs/hlse.md for instructions on how test data was generated
27 var result = '', i; 8 const stringFromBytes = function(bytes) {
9 let result = '';
28 10
29 for (i = 0; i < bytes.length; i++) { 11 for (let i = 0; i < bytes.length; i++) {
30 result += String.fromCharCode(bytes[i]); 12 result += String.fromCharCode(bytes[i]);
31 } 13 }
32 return result; 14 return result;
33 }; 15 };
34 16
35 QUnit.module('Decryption'); 17 QUnit.module('Decryption');
36 18 QUnit.test('decrypts a single AES-128 with PKCS7 block', function() {
37 test('decrypts a single AES-128 with PKCS7 block', function() { 19 let key = new Uint32Array([0, 0, 0, 0]);
38 var 20 let initVector = key;
39 key = new Uint32Array([0, 0, 0, 0]), 21 // the string "howdy folks" encrypted
40 initVector = key, 22 let encrypted = new Uint8Array([
41 // the string "howdy folks" encrypted 23 0xce, 0x90, 0x97, 0xd0,
42 encrypted = new Uint8Array([ 24 0x08, 0x46, 0x4d, 0x18,
43 0xce, 0x90, 0x97, 0xd0, 25 0x4f, 0xae, 0x01, 0x1c,
44 0x08, 0x46, 0x4d, 0x18, 26 0x82, 0xa8, 0xf0, 0x67
45 0x4f, 0xae, 0x01, 0x1c, 27 ]);
46 0x82, 0xa8, 0xf0, 0x67]); 28
47 29 QUnit.deepEqual('howdy folks',
48 deepEqual('howdy folks', 30 stringFromBytes(unpad(decrypt(encrypted, key, initVector))),
49 stringFromBytes(unpad(videojs.Hls.decrypt(encrypted, key, initVector))), 31 'decrypted with a byte array key'
50 'decrypted with a byte array key'); 32 );
51 }); 33 });
52 34
53 test('decrypts multiple AES-128 blocks with CBC', function() { 35 QUnit.test('decrypts multiple AES-128 blocks with CBC', function() {
54 var 36 let key = new Uint32Array([0, 0, 0, 0]);
55 key = new Uint32Array([0, 0, 0, 0]), 37 let initVector = key;
56 initVector = key,
57 // the string "0123456789abcdef01234" encrypted 38 // the string "0123456789abcdef01234" encrypted
58 encrypted = new Uint8Array([ 39 let encrypted = new Uint8Array([
59 0x14, 0xf5, 0xfe, 0x74, 40 0x14, 0xf5, 0xfe, 0x74,
60 0x69, 0x66, 0xf2, 0x92, 41 0x69, 0x66, 0xf2, 0x92,
61 0x65, 0x1c, 0x22, 0x88, 42 0x65, 0x1c, 0x22, 0x88,
62 0xbb, 0xff, 0x46, 0x09, 43 0xbb, 0xff, 0x46, 0x09,
63 44
64 0x0b, 0xde, 0x5e, 0x71, 45 0x0b, 0xde, 0x5e, 0x71,
65 0x77, 0x87, 0xeb, 0x84, 46 0x77, 0x87, 0xeb, 0x84,
66 0xa9, 0x54, 0xc2, 0x45, 47 0xa9, 0x54, 0xc2, 0x45,
67 0xe9, 0x4e, 0x29, 0xb3 48 0xe9, 0x4e, 0x29, 0xb3
68 ]); 49 ]);
69 50
70 deepEqual('0123456789abcdef01234', 51 QUnit.deepEqual('0123456789abcdef01234',
71 stringFromBytes(unpad(videojs.Hls.decrypt(encrypted, key, initVector))), 52 stringFromBytes(unpad(decrypt(encrypted, key, initVector))),
72 'decrypted multiple blocks'); 53 'decrypted multiple blocks');
73 }); 54 });
74 55
75 var clock;
76
77 QUnit.module('Incremental Processing', { 56 QUnit.module('Incremental Processing', {
78 setup: function() { 57 beforeEach() {
79 clock = sinon.useFakeTimers(); 58 this.clock = sinon.useFakeTimers();
80 }, 59 },
81 teardown: function() { 60 afterEach() {
82 clock.restore(); 61 this.clock.restore();
83 } 62 }
84 }); 63 });
85 64
86 test('executes a callback after a timeout', function() { 65 QUnit.test('executes a callback after a timeout', function() {
87 var asyncStream = new videojs.Hls.AsyncStream(), 66 let asyncStream = new AsyncStream();
88 calls = ''; 67 let calls = '';
68
89 asyncStream.push(function() { 69 asyncStream.push(function() {
90 calls += 'a'; 70 calls += 'a';
91 }); 71 });
92 72
93 clock.tick(asyncStream.delay); 73 this.clock.tick(asyncStream.delay);
94 equal(calls, 'a', 'invoked the callback once'); 74 QUnit.equal(calls, 'a', 'invoked the callback once');
95 clock.tick(asyncStream.delay); 75 this.clock.tick(asyncStream.delay);
96 equal(calls, 'a', 'only invoked the callback once'); 76 QUnit.equal(calls, 'a', 'only invoked the callback once');
97 }); 77 });
98 78
99 test('executes callback in series', function() { 79 QUnit.test('executes callback in series', function() {
100 var asyncStream = new videojs.Hls.AsyncStream(), 80 let asyncStream = new AsyncStream();
101 calls = ''; 81 let calls = '';
82
102 asyncStream.push(function() { 83 asyncStream.push(function() {
103 calls += 'a'; 84 calls += 'a';
104 }); 85 });
...@@ -106,62 +87,68 @@ test('executes callback in series', function() { ...@@ -106,62 +87,68 @@ test('executes callback in series', function() {
106 calls += 'b'; 87 calls += 'b';
107 }); 88 });
108 89
109 clock.tick(asyncStream.delay); 90 this.clock.tick(asyncStream.delay);
110 equal(calls, 'a', 'invoked the first callback'); 91 QUnit.equal(calls, 'a', 'invoked the first callback');
111 clock.tick(asyncStream.delay); 92 this.clock.tick(asyncStream.delay);
112 equal(calls, 'ab', 'invoked the second'); 93 QUnit.equal(calls, 'ab', 'invoked the second');
113 }); 94 });
114 95
115 var decrypter;
116
117 QUnit.module('Incremental Decryption', { 96 QUnit.module('Incremental Decryption', {
118 setup: function() { 97 beforeEach() {
119 clock = sinon.useFakeTimers(); 98 this.clock = sinon.useFakeTimers();
120 }, 99 },
121 teardown: function() { 100 afterEach() {
122 clock.restore(); 101 this.clock.restore();
123 } 102 }
124 }); 103 });
125 104
126 test('asynchronously decrypts a 4-word block', function() { 105 QUnit.test('asynchronously decrypts a 4-word block', function() {
127 var 106 let key = new Uint32Array([0, 0, 0, 0]);
128 key = new Uint32Array([0, 0, 0, 0]), 107 let initVector = key;
129 initVector = key, 108 // the string "howdy folks" encrypted
130 // the string "howdy folks" encrypted 109 let encrypted = new Uint8Array([
131 encrypted = new Uint8Array([ 110 0xce, 0x90, 0x97, 0xd0,
132 0xce, 0x90, 0x97, 0xd0, 111 0x08, 0x46, 0x4d, 0x18,
133 0x08, 0x46, 0x4d, 0x18, 112 0x4f, 0xae, 0x01, 0x1c,
134 0x4f, 0xae, 0x01, 0x1c, 113 0x82, 0xa8, 0xf0, 0x67
135 0x82, 0xa8, 0xf0, 0x67]), 114 ]);
136 decrypted; 115 let decrypted;
137 116 let decrypter = new Decrypter(
138 decrypter = new videojs.Hls.Decrypter(encrypted, key, initVector, function(error, result) { 117 encrypted,
139 decrypted = result; 118 key,
140 }); 119 initVector,
141 ok(!decrypted, 'asynchronously decrypts'); 120 function(error, result) {
142 121 if (error) {
143 clock.tick(decrypter.asyncStream_.delay * 2); 122 throw new Error(error);
144 123 }
145 ok(decrypted, 'completed decryption'); 124 decrypted = result;
146 deepEqual('howdy folks', 125 }
147 stringFromBytes(decrypted), 126 );
148 'decrypts and unpads the result'); 127
128 QUnit.ok(!decrypted, 'asynchronously decrypts');
129 this.clock.tick(decrypter.asyncStream_.delay * 2);
130
131 QUnit.ok(decrypted, 'completed decryption');
132 QUnit.deepEqual('howdy folks',
133 stringFromBytes(decrypted),
134 'decrypts and unpads the result');
149 }); 135 });
150 136
151 test('breaks up input greater than the step value', function() { 137 QUnit.test('breaks up input greater than the step value', function() {
152 var encrypted = new Int32Array(videojs.Hls.Decrypter.STEP + 4), 138 let encrypted = new Int32Array(Decrypter.STEP + 4);
153 done = false, 139 let done = false;
154 decrypter = new videojs.Hls.Decrypter(encrypted, 140 let decrypter = new Decrypter(
155 new Uint32Array(4), 141 encrypted,
156 new Uint32Array(4), 142 new Uint32Array(4),
157 function() { 143 new Uint32Array(4),
158 done = true; 144 function() {
159 }); 145 done = true;
160 clock.tick(decrypter.asyncStream_.delay * 2); 146 }
161 ok(!done, 'not finished after two ticks'); 147 );
162 148
163 clock.tick(decrypter.asyncStream_.delay); 149 this.clock.tick(decrypter.asyncStream_.delay * 2);
164 ok(done, 'finished after the last chunk is decrypted'); 150 QUnit.ok(!done, 'not finished after two ticks');
151
152 this.clock.tick(decrypter.asyncStream_.delay);
153 QUnit.ok(done, 'finished after the last chunk is decrypted');
165 }); 154 });
166
167 })(window, window.videojs, window.pkcs7.unpad);
......
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
12 <!-- NOTE in order for test to pass we require sinon 1.10.2 exactly --> 12 <!-- NOTE in order for test to pass we require sinon 1.10.2 exactly -->
13 <script src="/node_modules/sinon/pkg/sinon.js"></script> 13 <script src="/node_modules/sinon/pkg/sinon.js"></script>
14 <script src="/node_modules/qunitjs/qunit/qunit.js"></script> 14 <script src="/node_modules/qunitjs/qunit/qunit.js"></script>
15 <script src="/node_modules/pkcs7/dist/pkcs7.unpad.js"></script>
16 <script src="/node_modules/video.js/dist/video.js"></script> 15 <script src="/node_modules/video.js/dist/video.js"></script>
17 <script src="/node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js"></script> 16 <script src="/node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js"></script>
18 17
...@@ -21,14 +20,12 @@ ...@@ -21,14 +20,12 @@
21 <script src="/dist/videojs-contrib-hls.js"></script> 20 <script src="/dist/videojs-contrib-hls.js"></script>
22 <script src="/src/playlist.js"></script> 21 <script src="/src/playlist.js"></script>
23 <script src="/src/playlist-loader.js"></script> 22 <script src="/src/playlist-loader.js"></script>
24 <script src="/src/decrypter.js"></script>
25 <script src="/src/bin-utils.js"></script> 23 <script src="/src/bin-utils.js"></script>
26 24
27 <script src="/test/videojs-contrib-hls.test.js"></script> 25 <script src="/test/videojs-contrib-hls.test.js"></script>
28 <script src="/dist-test/videojs-contrib-hls.js"></script> 26 <script src="/dist-test/videojs-contrib-hls.js"></script>
29 <script src="/test/playlist.test.js"></script> 27 <script src="/test/playlist.test.js"></script>
30 <script src="/test/playlist-loader.test.js"></script> 28 <script src="/test/playlist-loader.test.js"></script>
31 <script src="/test/decrypter.test.js"></script>
32 29
33 </body> 30 </body>
34 </html> 31 </html>
......
...@@ -12,7 +12,6 @@ var DEFAULTS = { ...@@ -12,7 +12,6 @@ var DEFAULTS = {
12 'node_modules/video.js/dist/video-js.css', 12 'node_modules/video.js/dist/video-js.css',
13 13
14 // REMOVE ME WHEN BROWSERIFIED 14 // REMOVE ME WHEN BROWSERIFIED
15 'node_modules/pkcs7/dist/pkcs7.unpad.js',
16 'node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js', 15 'node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js',
17 16
18 // these two stub old functionality 17 // these two stub old functionality
...@@ -22,7 +21,6 @@ var DEFAULTS = { ...@@ -22,7 +21,6 @@ var DEFAULTS = {
22 21
23 'src/playlist.js', 22 'src/playlist.js',
24 'src/playlist-loader.js', 23 'src/playlist-loader.js',
25 'src/decrypter.js',
26 'src/bin-utils.js', 24 'src/bin-utils.js',
27 25
28 'test/stub.test.js', 26 'test/stub.test.js',
...@@ -47,7 +45,7 @@ var DEFAULTS = { ...@@ -47,7 +45,7 @@ var DEFAULTS = {
47 ], 45 ],
48 46
49 preprocessors: { 47 preprocessors: {
50 'test/{stub,m3u8}.test.js': ['browserify'] 48 'test/{decrypter,stub,m3u8}.test.js': ['browserify']
51 }, 49 },
52 50
53 reporters: ['dots'], 51 reporters: ['dots'],
......