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,16 +57,34 @@ ntoh = function(word) { ...@@ -58,16 +57,34 @@ 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 {
61 constructor(key) {
62 /**
63 * The expanded S-box and inverse S-box tables. These will be computed
64 * on the client so that we don't have to send them down the wire.
65 *
66 * There are two tables, _tables[0] is for encryption and
67 * _tables[1] is for decryption.
68 *
69 * The first 4 sub-tables are the expanded S-box with MixColumns. The
70 * last (_tables[01][4]) is the S-box itself.
71 *
72 * @private
73 */
74 this._tables = [[[], [], [], [], []], [[], [], [], [], []]];
62 this._precompute(); 75 this._precompute();
63 76 let i;
64 var i, j, tmp, 77 let j;
65 encKey, decKey, 78 let tmp;
66 sbox = this._tables[0][4], decTable = this._tables[1], 79 let encKey;
67 keyLen = key.length, rcon = 1; 80 let decKey;
81 let sbox = this._tables[0][4];
82 let decTable = this._tables[1];
83 let keyLen = key.length;
84 let rcon = 1;
68 85
69 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { 86 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
70 throw new Error("Invalid aes key size"); 87 throw new Error('Invalid aes key size');
71 } 88 }
72 89
73 encKey = key.slice(0); 90 encKey = key.slice(0);
...@@ -76,81 +93,155 @@ AES = function (key) { ...@@ -76,81 +93,155 @@ AES = function (key) {
76 93
77 // schedule encryption keys 94 // schedule encryption keys
78 for (i = keyLen; i < 4 * keyLen + 28; i++) { 95 for (i = keyLen; i < 4 * keyLen + 28; i++) {
79 tmp = encKey[i-1]; 96 tmp = encKey[i - 1];
80 97
81 // apply sbox 98 // apply sbox
82 if (i%keyLen === 0 || (keyLen === 8 && i%keyLen === 4)) { 99 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]; 100 tmp = sbox[tmp >>> 24] << 24 ^
101 sbox[tmp >> 16 & 255] << 16 ^
102 sbox[tmp >> 8 & 255] << 8 ^
103 sbox[tmp & 255];
84 104
85 // shift rows and add rcon 105 // shift rows and add rcon
86 if (i%keyLen === 0) { 106 if (i % keyLen === 0) {
87 tmp = tmp<<8 ^ tmp>>>24 ^ rcon<<24; 107 tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
88 rcon = rcon<<1 ^ (rcon>>7)*283; 108 rcon = rcon << 1 ^ (rcon >> 7) * 283;
89 } 109 }
90 } 110 }
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]];
105 } 125 }
106 } 126 }
107 }; 127 }
108 128
109 AES.prototype = {
110 /** 129 /**
111 * The expanded S-box and inverse S-box tables. These will be computed 130 * Decrypt 16 bytes, specified as four 32-bit words.
112 * on the client so that we don't have to send them down the wire. 131 * @param encrypted0 {number} the first word to decrypt
113 * 132 * @param encrypted1 {number} the second word to decrypt
114 * There are two tables, _tables[0] is for encryption and 133 * @param encrypted2 {number} the third word to decrypt
115 * _tables[1] is for decryption. 134 * @param encrypted3 {number} the fourth word to decrypt
116 * 135 * @param out {Int32Array} the array to write the decrypted words
117 * The first 4 sub-tables are the expanded S-box with MixColumns. The 136 * into
118 * last (_tables[01][4]) is the S-box itself. 137 * @param offset {number} the offset into the output array to start
119 * 138 * writing results
120 * @private 139 * @return {Array} The plaintext.
121 */ 140 */
122 _tables: [[[],[],[],[],[]],[[],[],[],[],[]]], 141 decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
142 let key = this._key[1];
143 // state variables a,b,c,d are loaded with pre-whitened data
144 let a = encrypted0 ^ key[0];
145 let b = encrypted3 ^ key[1];
146 let c = encrypted2 ^ key[2];
147 let d = encrypted1 ^ key[3];
148 let a2;
149 let b2;
150 let c2;
151
152 // key.length === 2 ?
153 let nInnerRounds = key.length / 4 - 2;
154 let i;
155 let kIndex = 4;
156 let table = this._tables[1];
157
158 // load up the tables
159 let table0 = table[0];
160 let table1 = table[1];
161 let table2 = table[2];
162 let table3 = table[3];
163 let sbox = table[4];
164
165 // Inner rounds. Cribbed from OpenSSL.
166 for (i = 0; i < nInnerRounds; i++) {
167 a2 = table0[a >>> 24] ^
168 table1[b >> 16 & 255] ^
169 table2[c >> 8 & 255] ^
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];
187 kIndex += 4;
188 a = a2; b = b2; c = c2;
189 }
190
191 // Last round.
192 for (i = 0; i < 4; i++) {
193 out[(3 & -i) + offset] =
194 sbox[a >>> 24] << 24 ^
195 sbox[b >> 16 & 255] << 16 ^
196 sbox[c >> 8 & 255] << 8 ^
197 sbox[d & 255] ^
198 key[kIndex++];
199 a2 = a; a = b; b = c; c = d; d = a2;
200 }
201 }
123 202
124 /** 203 /**
125 * Expand the S-box tables. 204 * Expand the S-box tables.
126 * 205 *
127 * @private 206 * @private
128 */ 207 */
129 _precompute: function () { 208 _precompute() {
130 var encTable = this._tables[0], decTable = this._tables[1], 209 let encTable = this._tables[0];
131 sbox = encTable[4], sboxInv = decTable[4], 210 let decTable = this._tables[1];
132 i, x, xInv, d=[], th=[], x2, x4, x8, s, tEnc, tDec; 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;
133 224
134 // Compute double and third tables 225 // Compute double and third tables
135 for (i = 0; i < 256; i++) { 226 for (i = 0; i < 256; i++) {
136 th[( d[i] = i<<1 ^ (i>>7)*283 )^i]=i; 227 th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
137 } 228 }
138 229
139 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { 230 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
140 // Compute sbox 231 // Compute sbox
141 s = xInv ^ xInv<<1 ^ xInv<<2 ^ xInv<<3 ^ xInv<<4; 232 s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
142 s = s>>8 ^ s&255 ^ 99; 233 s = s >> 8 ^ s & 255 ^ 99;
143 sbox[x] = s; 234 sbox[x] = s;
144 sboxInv[s] = x; 235 sboxInv[s] = x;
145 236
146 // Compute MixColumns 237 // Compute MixColumns
147 x8 = d[x4 = d[x2 = d[x]]]; 238 x8 = d[x4 = d[x2 = d[x]]];
148 tDec = x8*0x1010101 ^ x4*0x10001 ^ x2*0x101 ^ x*0x1010100; 239 tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
149 tEnc = d[s]*0x101 ^ s*0x1010100; 240 tEnc = d[s] * 0x101 ^ s * 0x1010100;
150 241
151 for (i = 0; i < 4; i++) { 242 for (i = 0; i < 4; i++) {
152 encTable[i][x] = tEnc = tEnc<<24 ^ tEnc>>>8; 243 encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
153 decTable[i][s] = tDec = tDec<<24 ^ tDec>>>8; 244 decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
154 } 245 }
155 } 246 }
156 247
...@@ -159,64 +250,10 @@ AES.prototype = { ...@@ -159,64 +250,10 @@ AES.prototype = {
159 encTable[i] = encTable[i].slice(0); 250 encTable[i] = encTable[i].slice(0);
160 decTable[i] = decTable[i].slice(0); 251 decTable[i] = decTable[i].slice(0);
161 } 252 }
162 },
163
164 /**
165 * Decrypt 16 bytes, specified as four 32-bit words.
166 * @param encrypted0 {number} the first word to decrypt
167 * @param encrypted1 {number} the second word to decrypt
168 * @param encrypted2 {number} the third word to decrypt
169 * @param encrypted3 {number} the fourth word to decrypt
170 * @param out {Int32Array} the array to write the decrypted words
171 * into
172 * @param offset {number} the offset into the output array to start
173 * writing results
174 * @return {Array} The plaintext.
175 */
176 decrypt:function (encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
177 var key = this._key[1],
178 // state variables a,b,c,d are loaded with pre-whitened data
179 a = encrypted0 ^ key[0],
180 b = encrypted3 ^ key[1],
181 c = encrypted2 ^ key[2],
182 d = encrypted1 ^ key[3],
183 a2, b2, c2,
184
185 nInnerRounds = key.length / 4 - 2, // key.length === 2 ?
186 i,
187 kIndex = 4,
188 table = this._tables[1],
189
190 // load up the tables
191 table0 = table[0],
192 table1 = table[1],
193 table2 = table[2],
194 table3 = table[3],
195 sbox = table[4];
196
197 // Inner rounds. Cribbed from OpenSSL.
198 for (i = 0; i < nInnerRounds; i++) {
199 a2 = table0[a>>>24] ^ table1[b>>16 & 255] ^ table2[c>>8 & 255] ^ table3[d & 255] ^ key[kIndex];
200 b2 = table0[b>>>24] ^ table1[c>>16 & 255] ^ table2[d>>8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
201 c2 = table0[c>>>24] ^ table1[d>>16 & 255] ^ table2[a>>8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
202 d = table0[d>>>24] ^ table1[a>>16 & 255] ^ table2[b>>8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
203 kIndex += 4;
204 a=a2; b=b2; c=c2;
205 }
206
207 // Last round.
208 for (i = 0; i < 4; i++) {
209 out[(3 & -i) + offset] =
210 sbox[a>>>24 ]<<24 ^
211 sbox[b>>16 & 255]<<16 ^
212 sbox[c>>8 & 255]<<8 ^
213 sbox[d & 255] ^
214 key[kIndex++];
215 a2=a; a=b; b=c; c=d; d=a2;
216 }
217 } 253 }
218 }; 254 }
219 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(
273 encrypted.buffer,
274 encrypted.byteOffset,
275 encrypted.byteLength >> 2
276 );
236 277
237 decipher = new AES(Array.prototype.slice.call(key)), 278 let decipher = new AES(Array.prototype.slice.call(key));
238 279
239 // byte and word-level access for the decrypted output 280 // byte and word-level access for the decrypted output
240 decrypted = new Uint8Array(encrypted.byteLength), 281 let decrypted = new Uint8Array(encrypted.byteLength);
241 decrypted32 = new Int32Array(decrypted.buffer), 282 let decrypted32 = new Int32Array(decrypted.buffer);
242 283
243 // temporary variables for working with the IV, encrypted, and 284 // temporary variables for working with the IV, encrypted, and
244 // decrypted data 285 // decrypted data
245 init0, init1, init2, init3, 286 let init0;
246 encrypted0, encrypted1, encrypted2, encrypted3, 287 let init1;
288 let init2;
289 let init3;
290 let encrypted0;
291 let encrypted1;
292 let encrypted2;
293 let encrypted3;
247 294
248 // iteration variable 295 // iteration variable
249 wordIx; 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,12 +313,14 @@ decrypt = function(encrypted, key, initVector) { ...@@ -266,12 +313,14 @@ 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(
317 encrypted0,
270 encrypted1, 318 encrypted1,
271 encrypted2, 319 encrypted2,
272 encrypted3, 320 encrypted3,
273 decrypted32, 321 decrypted32,
274 wordIx); 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
...@@ -290,35 +339,46 @@ decrypt = function(encrypted, key, initVector) { ...@@ -290,35 +339,46 @@ decrypt = function(encrypted, key, initVector) {
290 return decrypted; 339 return decrypted;
291 }; 340 };
292 341
293 AsyncStream = function() { 342 export class AsyncStream extends Stream {
343 constructor() {
344 super(Stream);
294 this.jobs = []; 345 this.jobs = [];
295 this.delay = 1; 346 this.delay = 1;
296 this.timeout_ = null; 347 this.timeout_ = null;
297 }; 348 }
298 AsyncStream.prototype = new videojs.Hls.Stream(); 349 processJob_() {
299 AsyncStream.prototype.processJob_ = function() {
300 this.jobs.shift()(); 350 this.jobs.shift()();
301 if (this.jobs.length) { 351 if (this.jobs.length) {
302 this.timeout_ = setTimeout(this.processJob_.bind(this), 352 this.timeout_ = setTimeout(
303 this.delay); 353 this.processJob_.bind(this),
354 this.delay
355 );
304 } else { 356 } else {
305 this.timeout_ = null; 357 this.timeout_ = null;
306 } 358 }
307 }; 359 }
308 AsyncStream.prototype.push = function(job) { 360 push(job) {
309 this.jobs.push(job); 361 this.jobs.push(job);
310 if (!this.timeout_) { 362 if (!this.timeout_) {
311 this.timeout_ = setTimeout(this.processJob_.bind(this), 363 this.timeout_ = setTimeout(
312 this.delay); 364 this.processJob_.bind(this),
365 this.delay
366 );
313 } 367 }
314 }; 368 }
369 }
370
371 // the maximum number of bytes to process at one time
372 const decrypterStep = 4 * 8000;
373
374 export class Decrypter extends Stream {
375 constructor(encrypted, key, initVector, done) {
376 super(Stream);
377 let step = decrypterStep;
378 let encrypted32 = new Int32Array(encrypted.buffer);
379 let decrypted = new Uint8Array(encrypted.byteLength);
380 let i = 0;
315 381
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(); 382 this.asyncStream_ = new AsyncStream();
323 383
324 // split up the encryption job and do the individual chunks asynchronously 384 // split up the encryption job and do the individual chunks asynchronously
...@@ -327,12 +387,10 @@ Decrypter = function(encrypted, key, initVector, done) { ...@@ -327,12 +387,10 @@ Decrypter = function(encrypted, key, initVector, done) {
327 initVector, 387 initVector,
328 decrypted)); 388 decrypted));
329 for (i = step; i < encrypted32.length; i += step) { 389 for (i = step; i < encrypted32.length; i += step) {
330 initVector = new Uint32Array([ 390 initVector = new Uint32Array([ntoh(encrypted32[i - 4]),
331 ntoh(encrypted32[i - 4]),
332 ntoh(encrypted32[i - 3]), 391 ntoh(encrypted32[i - 3]),
333 ntoh(encrypted32[i - 2]), 392 ntoh(encrypted32[i - 2]),
334 ntoh(encrypted32[i - 1]) 393 ntoh(encrypted32[i - 1])]);
335 ]);
336 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), 394 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step),
337 key, 395 key,
338 initVector, 396 initVector,
...@@ -343,22 +401,20 @@ Decrypter = function(encrypted, key, initVector, done) { ...@@ -343,22 +401,20 @@ Decrypter = function(encrypted, key, initVector, done) {
343 // remove pkcs#7 padding from the decrypted bytes 401 // remove pkcs#7 padding from the decrypted bytes
344 done(null, unpad(decrypted)); 402 done(null, unpad(decrypted));
345 }); 403 });
346 }; 404 }
347 Decrypter.prototype = new videojs.Hls.Stream(); 405 decryptChunk_(encrypted, key, initVector, decrypted) {
348 Decrypter.prototype.decryptChunk_ = function(encrypted, key, initVector, decrypted) {
349 return function() { 406 return function() {
350 var bytes = decrypt(encrypted, 407 let bytes = decrypt(encrypted, key, initVector);
351 key, 408
352 initVector);
353 decrypted.set(bytes, encrypted.byteOffset); 409 decrypted.set(bytes, encrypted.byteOffset);
354 }; 410 };
355 }; 411 }
356 // the maximum number of bytes to process at one time 412 }
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]),
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([
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,
...@@ -67,38 +48,38 @@ test('decrypts multiple AES-128 blocks with CBC', function() { ...@@ -67,38 +48,38 @@ test('decrypts multiple AES-128 blocks with CBC', function() {
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,
130 // the string "howdy folks" encrypted 108 // the string "howdy folks" encrypted
131 encrypted = new Uint8Array([ 109 let encrypted = new Uint8Array([
132 0xce, 0x90, 0x97, 0xd0, 110 0xce, 0x90, 0x97, 0xd0,
133 0x08, 0x46, 0x4d, 0x18, 111 0x08, 0x46, 0x4d, 0x18,
134 0x4f, 0xae, 0x01, 0x1c, 112 0x4f, 0xae, 0x01, 0x1c,
135 0x82, 0xa8, 0xf0, 0x67]), 113 0x82, 0xa8, 0xf0, 0x67
136 decrypted; 114 ]);
137 115 let decrypted;
138 decrypter = new videojs.Hls.Decrypter(encrypted, key, initVector, function(error, result) { 116 let decrypter = new Decrypter(
117 encrypted,
118 key,
119 initVector,
120 function(error, result) {
121 if (error) {
122 throw new Error(error);
123 }
139 decrypted = result; 124 decrypted = result;
140 }); 125 }
141 ok(!decrypted, 'asynchronously decrypts'); 126 );
142 127
143 clock.tick(decrypter.asyncStream_.delay * 2); 128 QUnit.ok(!decrypted, 'asynchronously decrypts');
129 this.clock.tick(decrypter.asyncStream_.delay * 2);
144 130
145 ok(decrypted, 'completed decryption'); 131 QUnit.ok(decrypted, 'completed decryption');
146 deepEqual('howdy folks', 132 QUnit.deepEqual('howdy folks',
147 stringFromBytes(decrypted), 133 stringFromBytes(decrypted),
148 'decrypts and unpads the result'); 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(
141 encrypted,
155 new Uint32Array(4), 142 new Uint32Array(4),
156 new Uint32Array(4), 143 new Uint32Array(4),
157 function() { 144 function() {
158 done = true; 145 done = true;
159 }); 146 }
160 clock.tick(decrypter.asyncStream_.delay * 2); 147 );
161 ok(!done, 'not finished after two ticks');
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');
165 });
166 151
167 })(window, window.videojs, window.pkcs7.unpad); 152 this.clock.tick(decrypter.asyncStream_.delay);
153 QUnit.ok(done, 'finished after the last chunk is decrypted');
154 });
......
...@@ -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'],
......