97d703e4 by David LaPalomento

Merge pull request #540 from BrandonOCasey/browserify-p3

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