seperate aes-decrypter from hls (#748)
Showing
9 changed files
with
7 additions
and
687 deletions
... | @@ -11,7 +11,7 @@ Play back HLS with video.js, even where it's not natively supported. | ... | @@ -11,7 +11,7 @@ Play back HLS with video.js, even where it's not natively supported. |
11 | - [Installation](#installation) | 11 | - [Installation](#installation) |
12 | - [NPM](#npm) | 12 | - [NPM](#npm) |
13 | - [CDN](#cdn) | 13 | - [CDN](#cdn) |
14 | - [Local](#local) | 14 | - [Releases](#releases) |
15 | - [Manual Build](#manual-build) | 15 | - [Manual Build](#manual-build) |
16 | - [Contributing](#contributing) | 16 | - [Contributing](#contributing) |
17 | - [Getting Started](#getting-started) | 17 | - [Getting Started](#getting-started) |
... | @@ -42,7 +42,9 @@ Play back HLS with video.js, even where it's not natively supported. | ... | @@ -42,7 +42,9 @@ Play back HLS with video.js, even where it's not natively supported. |
42 | - [Testing](#testing) | 42 | - [Testing](#testing) |
43 | - [Release History](#release-history) | 43 | - [Release History](#release-history) |
44 | - [Building](#building) | 44 | - [Building](#building) |
45 | - [Development Commands](#development-commands) | 45 | - [Development](#development) |
46 | - [Tools](#tools) | ||
47 | - [Commands](#commands) | ||
46 | 48 | ||
47 | <!-- END doctoc generated TOC please keep comment here to allow auto update --> | 49 | <!-- END doctoc generated TOC please keep comment here to allow auto update --> |
48 | 50 | ... | ... |
... | @@ -84,7 +84,7 @@ | ... | @@ -84,7 +84,7 @@ |
84 | ], | 84 | ], |
85 | "dependencies": { | 85 | "dependencies": { |
86 | "m3u8-parser": "^1.0.2", | 86 | "m3u8-parser": "^1.0.2", |
87 | "pkcs7": "^0.2.2", | 87 | "aes-decrypter": "^1.0.3", |
88 | "video.js": "^5.10.1", | 88 | "video.js": "^5.10.1", |
89 | "videojs-contrib-media-sources": "^3.1.0", | 89 | "videojs-contrib-media-sources": "^3.1.0", |
90 | "videojs-swf": "^5.0.2" | 90 | "videojs-swf": "^5.0.2" | ... | ... |
src/decrypter/aes.js
deleted
100644 → 0
1 | /** | ||
2 | * @file decrypter/aes.js | ||
3 | * | ||
4 | * This file contains an adaptation of the AES decryption algorithm | ||
5 | * from the Standford Javascript Cryptography Library. That work is | ||
6 | * covered by the following copyright and permissions notice: | ||
7 | * | ||
8 | * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh. | ||
9 | * All rights reserved. | ||
10 | * | ||
11 | * Redistribution and use in source and binary forms, with or without | ||
12 | * modification, are permitted provided that the following conditions are | ||
13 | * met: | ||
14 | * | ||
15 | * 1. Redistributions of source code must retain the above copyright | ||
16 | * notice, this list of conditions and the following disclaimer. | ||
17 | * | ||
18 | * 2. Redistributions in binary form must reproduce the above | ||
19 | * copyright notice, this list of conditions and the following | ||
20 | * disclaimer in the documentation and/or other materials provided | ||
21 | * with the distribution. | ||
22 | * | ||
23 | * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR | ||
24 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
25 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
26 | * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE | ||
27 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
28 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
29 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR | ||
30 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | ||
31 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | ||
32 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN | ||
33 | * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
34 | * | ||
35 | * The views and conclusions contained in the software and documentation | ||
36 | * are those of the authors and should not be interpreted as representing | ||
37 | * official policies, either expressed or implied, of the authors. | ||
38 | */ | ||
39 | |||
40 | /** | ||
41 | * Expand the S-box tables. | ||
42 | * | ||
43 | * @private | ||
44 | */ | ||
45 | const precompute = function() { | ||
46 | let tables = [[[], [], [], [], []], [[], [], [], [], []]]; | ||
47 | let encTable = tables[0]; | ||
48 | let decTable = tables[1]; | ||
49 | let sbox = encTable[4]; | ||
50 | let sboxInv = decTable[4]; | ||
51 | let i; | ||
52 | let x; | ||
53 | let xInv; | ||
54 | let d = []; | ||
55 | let th = []; | ||
56 | let x2; | ||
57 | let x4; | ||
58 | let x8; | ||
59 | let s; | ||
60 | let tEnc; | ||
61 | let tDec; | ||
62 | |||
63 | // Compute double and third tables | ||
64 | for (i = 0; i < 256; i++) { | ||
65 | th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i; | ||
66 | } | ||
67 | |||
68 | for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { | ||
69 | // Compute sbox | ||
70 | s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4; | ||
71 | s = s >> 8 ^ s & 255 ^ 99; | ||
72 | sbox[x] = s; | ||
73 | sboxInv[s] = x; | ||
74 | |||
75 | // Compute MixColumns | ||
76 | x8 = d[x4 = d[x2 = d[x]]]; | ||
77 | tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; | ||
78 | tEnc = d[s] * 0x101 ^ s * 0x1010100; | ||
79 | |||
80 | for (i = 0; i < 4; i++) { | ||
81 | encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8; | ||
82 | decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8; | ||
83 | } | ||
84 | } | ||
85 | |||
86 | // Compactify. Considerable speedup on Firefox. | ||
87 | for (i = 0; i < 5; i++) { | ||
88 | encTable[i] = encTable[i].slice(0); | ||
89 | decTable[i] = decTable[i].slice(0); | ||
90 | } | ||
91 | return tables; | ||
92 | }; | ||
93 | let aesTables = null; | ||
94 | |||
95 | /** | ||
96 | * Schedule out an AES key for both encryption and decryption. This | ||
97 | * is a low-level class. Use a cipher mode to do bulk encryption. | ||
98 | * | ||
99 | * @class AES | ||
100 | * @param key {Array} The key as an array of 4, 6 or 8 words. | ||
101 | */ | ||
102 | export default class AES { | ||
103 | constructor(key) { | ||
104 | /** | ||
105 | * The expanded S-box and inverse S-box tables. These will be computed | ||
106 | * on the client so that we don't have to send them down the wire. | ||
107 | * | ||
108 | * There are two tables, _tables[0] is for encryption and | ||
109 | * _tables[1] is for decryption. | ||
110 | * | ||
111 | * The first 4 sub-tables are the expanded S-box with MixColumns. The | ||
112 | * last (_tables[01][4]) is the S-box itself. | ||
113 | * | ||
114 | * @private | ||
115 | */ | ||
116 | // if we have yet to precompute the S-box tables | ||
117 | // do so now | ||
118 | if (!aesTables) { | ||
119 | aesTables = precompute(); | ||
120 | } | ||
121 | // then make a copy of that object for use | ||
122 | this._tables = [[aesTables[0][0].slice(), | ||
123 | aesTables[0][1].slice(), | ||
124 | aesTables[0][2].slice(), | ||
125 | aesTables[0][3].slice(), | ||
126 | aesTables[0][4].slice()], | ||
127 | [aesTables[1][0].slice(), | ||
128 | aesTables[1][1].slice(), | ||
129 | aesTables[1][2].slice(), | ||
130 | aesTables[1][3].slice(), | ||
131 | aesTables[1][4].slice()]]; | ||
132 | let i; | ||
133 | let j; | ||
134 | let tmp; | ||
135 | let encKey; | ||
136 | let decKey; | ||
137 | let sbox = this._tables[0][4]; | ||
138 | let decTable = this._tables[1]; | ||
139 | let keyLen = key.length; | ||
140 | let rcon = 1; | ||
141 | |||
142 | if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { | ||
143 | throw new Error('Invalid aes key size'); | ||
144 | } | ||
145 | |||
146 | encKey = key.slice(0); | ||
147 | decKey = []; | ||
148 | this._key = [encKey, decKey]; | ||
149 | |||
150 | // schedule encryption keys | ||
151 | for (i = keyLen; i < 4 * keyLen + 28; i++) { | ||
152 | tmp = encKey[i - 1]; | ||
153 | |||
154 | // apply sbox | ||
155 | if (i % keyLen === 0 || (keyLen === 8 && i % keyLen === 4)) { | ||
156 | tmp = sbox[tmp >>> 24] << 24 ^ | ||
157 | sbox[tmp >> 16 & 255] << 16 ^ | ||
158 | sbox[tmp >> 8 & 255] << 8 ^ | ||
159 | sbox[tmp & 255]; | ||
160 | |||
161 | // shift rows and add rcon | ||
162 | if (i % keyLen === 0) { | ||
163 | tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24; | ||
164 | rcon = rcon << 1 ^ (rcon >> 7) * 283; | ||
165 | } | ||
166 | } | ||
167 | |||
168 | encKey[i] = encKey[i - keyLen] ^ tmp; | ||
169 | } | ||
170 | |||
171 | // schedule decryption keys | ||
172 | for (j = 0; i; j++, i--) { | ||
173 | tmp = encKey[j & 3 ? i : i - 4]; | ||
174 | if (i <= 4 || j < 4) { | ||
175 | decKey[j] = tmp; | ||
176 | } else { | ||
177 | decKey[j] = decTable[0][sbox[tmp >>> 24 ]] ^ | ||
178 | decTable[1][sbox[tmp >> 16 & 255]] ^ | ||
179 | decTable[2][sbox[tmp >> 8 & 255]] ^ | ||
180 | decTable[3][sbox[tmp & 255]]; | ||
181 | } | ||
182 | } | ||
183 | } | ||
184 | |||
185 | /** | ||
186 | * Decrypt 16 bytes, specified as four 32-bit words. | ||
187 | * | ||
188 | * @param {Number} encrypted0 the first word to decrypt | ||
189 | * @param {Number} encrypted1 the second word to decrypt | ||
190 | * @param {Number} encrypted2 the third word to decrypt | ||
191 | * @param {Number} encrypted3 the fourth word to decrypt | ||
192 | * @param {Int32Array} out the array to write the decrypted words | ||
193 | * into | ||
194 | * @param {Number} offset the offset into the output array to start | ||
195 | * writing results | ||
196 | * @return {Array} The plaintext. | ||
197 | */ | ||
198 | decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) { | ||
199 | let key = this._key[1]; | ||
200 | // state variables a,b,c,d are loaded with pre-whitened data | ||
201 | let a = encrypted0 ^ key[0]; | ||
202 | let b = encrypted3 ^ key[1]; | ||
203 | let c = encrypted2 ^ key[2]; | ||
204 | let d = encrypted1 ^ key[3]; | ||
205 | let a2; | ||
206 | let b2; | ||
207 | let c2; | ||
208 | |||
209 | // key.length === 2 ? | ||
210 | let nInnerRounds = key.length / 4 - 2; | ||
211 | let i; | ||
212 | let kIndex = 4; | ||
213 | let table = this._tables[1]; | ||
214 | |||
215 | // load up the tables | ||
216 | let table0 = table[0]; | ||
217 | let table1 = table[1]; | ||
218 | let table2 = table[2]; | ||
219 | let table3 = table[3]; | ||
220 | let sbox = table[4]; | ||
221 | |||
222 | // Inner rounds. Cribbed from OpenSSL. | ||
223 | for (i = 0; i < nInnerRounds; i++) { | ||
224 | a2 = table0[a >>> 24] ^ | ||
225 | table1[b >> 16 & 255] ^ | ||
226 | table2[c >> 8 & 255] ^ | ||
227 | table3[d & 255] ^ | ||
228 | key[kIndex]; | ||
229 | b2 = table0[b >>> 24] ^ | ||
230 | table1[c >> 16 & 255] ^ | ||
231 | table2[d >> 8 & 255] ^ | ||
232 | table3[a & 255] ^ | ||
233 | key[kIndex + 1]; | ||
234 | c2 = table0[c >>> 24] ^ | ||
235 | table1[d >> 16 & 255] ^ | ||
236 | table2[a >> 8 & 255] ^ | ||
237 | table3[b & 255] ^ | ||
238 | key[kIndex + 2]; | ||
239 | d = table0[d >>> 24] ^ | ||
240 | table1[a >> 16 & 255] ^ | ||
241 | table2[b >> 8 & 255] ^ | ||
242 | table3[c & 255] ^ | ||
243 | key[kIndex + 3]; | ||
244 | kIndex += 4; | ||
245 | a = a2; b = b2; c = c2; | ||
246 | } | ||
247 | |||
248 | // Last round. | ||
249 | for (i = 0; i < 4; i++) { | ||
250 | out[(3 & -i) + offset] = | ||
251 | sbox[a >>> 24] << 24 ^ | ||
252 | sbox[b >> 16 & 255] << 16 ^ | ||
253 | sbox[c >> 8 & 255] << 8 ^ | ||
254 | sbox[d & 255] ^ | ||
255 | key[kIndex++]; | ||
256 | a2 = a; a = b; b = c; c = d; d = a2; | ||
257 | } | ||
258 | } | ||
259 | } |
src/decrypter/async-stream.js
deleted
100644 → 0
1 | /** | ||
2 | * @file decrypter/async-stream.js | ||
3 | */ | ||
4 | import Stream from '../stream'; | ||
5 | |||
6 | /** | ||
7 | * A wrapper around the Stream class to use setTiemout | ||
8 | * and run stream "jobs" Asynchronously | ||
9 | * | ||
10 | * @class AsyncStream | ||
11 | * @extends Stream | ||
12 | */ | ||
13 | export default class AsyncStream extends Stream { | ||
14 | constructor() { | ||
15 | super(Stream); | ||
16 | this.jobs = []; | ||
17 | this.delay = 1; | ||
18 | this.timeout_ = null; | ||
19 | } | ||
20 | |||
21 | /** | ||
22 | * process an async job | ||
23 | * | ||
24 | * @private | ||
25 | */ | ||
26 | processJob_() { | ||
27 | this.jobs.shift()(); | ||
28 | if (this.jobs.length) { | ||
29 | this.timeout_ = setTimeout(this.processJob_.bind(this), | ||
30 | this.delay); | ||
31 | } else { | ||
32 | this.timeout_ = null; | ||
33 | } | ||
34 | } | ||
35 | |||
36 | /** | ||
37 | * push a job into the stream | ||
38 | * | ||
39 | * @param {Function} job the job to push into the stream | ||
40 | */ | ||
41 | push(job) { | ||
42 | this.jobs.push(job); | ||
43 | if (!this.timeout_) { | ||
44 | this.timeout_ = setTimeout(this.processJob_.bind(this), | ||
45 | this.delay); | ||
46 | } | ||
47 | } | ||
48 | } | ||
49 |
src/decrypter/decrypter.js
deleted
100644 → 0
1 | /** | ||
2 | * @file decrypter/decrypter.js | ||
3 | * | ||
4 | * An asynchronous implementation of AES-128 CBC decryption with | ||
5 | * PKCS#7 padding. | ||
6 | */ | ||
7 | |||
8 | import AES from './aes'; | ||
9 | import AsyncStream from './async-stream'; | ||
10 | import {unpad} from 'pkcs7'; | ||
11 | |||
12 | /** | ||
13 | * Convert network-order (big-endian) bytes into their little-endian | ||
14 | * representation. | ||
15 | */ | ||
16 | const ntoh = function(word) { | ||
17 | return (word << 24) | | ||
18 | ((word & 0xff00) << 8) | | ||
19 | ((word & 0xff0000) >> 8) | | ||
20 | (word >>> 24); | ||
21 | }; | ||
22 | |||
23 | /** | ||
24 | * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. | ||
25 | * | ||
26 | * @param {Uint8Array} encrypted the encrypted bytes | ||
27 | * @param {Uint32Array} key the bytes of the decryption key | ||
28 | * @param {Uint32Array} initVector the initialization vector (IV) to | ||
29 | * use for the first round of CBC. | ||
30 | * @return {Uint8Array} the decrypted bytes | ||
31 | * | ||
32 | * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard | ||
33 | * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 | ||
34 | * @see https://tools.ietf.org/html/rfc2315 | ||
35 | */ | ||
36 | export const decrypt = function(encrypted, key, initVector) { | ||
37 | // word-level access to the encrypted bytes | ||
38 | let encrypted32 = new Int32Array(encrypted.buffer, | ||
39 | encrypted.byteOffset, | ||
40 | encrypted.byteLength >> 2); | ||
41 | |||
42 | let decipher = new AES(Array.prototype.slice.call(key)); | ||
43 | |||
44 | // byte and word-level access for the decrypted output | ||
45 | let decrypted = new Uint8Array(encrypted.byteLength); | ||
46 | let decrypted32 = new Int32Array(decrypted.buffer); | ||
47 | |||
48 | // temporary variables for working with the IV, encrypted, and | ||
49 | // decrypted data | ||
50 | let init0; | ||
51 | let init1; | ||
52 | let init2; | ||
53 | let init3; | ||
54 | let encrypted0; | ||
55 | let encrypted1; | ||
56 | let encrypted2; | ||
57 | let encrypted3; | ||
58 | |||
59 | // iteration variable | ||
60 | let wordIx; | ||
61 | |||
62 | // pull out the words of the IV to ensure we don't modify the | ||
63 | // passed-in reference and easier access | ||
64 | init0 = initVector[0]; | ||
65 | init1 = initVector[1]; | ||
66 | init2 = initVector[2]; | ||
67 | init3 = initVector[3]; | ||
68 | |||
69 | // decrypt four word sequences, applying cipher-block chaining (CBC) | ||
70 | // to each decrypted block | ||
71 | for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) { | ||
72 | // convert big-endian (network order) words into little-endian | ||
73 | // (javascript order) | ||
74 | encrypted0 = ntoh(encrypted32[wordIx]); | ||
75 | encrypted1 = ntoh(encrypted32[wordIx + 1]); | ||
76 | encrypted2 = ntoh(encrypted32[wordIx + 2]); | ||
77 | encrypted3 = ntoh(encrypted32[wordIx + 3]); | ||
78 | |||
79 | // decrypt the block | ||
80 | decipher.decrypt(encrypted0, | ||
81 | encrypted1, | ||
82 | encrypted2, | ||
83 | encrypted3, | ||
84 | decrypted32, | ||
85 | wordIx); | ||
86 | |||
87 | // XOR with the IV, and restore network byte-order to obtain the | ||
88 | // plaintext | ||
89 | decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); | ||
90 | decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); | ||
91 | decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); | ||
92 | decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); | ||
93 | |||
94 | // setup the IV for the next round | ||
95 | init0 = encrypted0; | ||
96 | init1 = encrypted1; | ||
97 | init2 = encrypted2; | ||
98 | init3 = encrypted3; | ||
99 | } | ||
100 | |||
101 | return decrypted; | ||
102 | }; | ||
103 | |||
104 | /** | ||
105 | * The `Decrypter` class that manages decryption of AES | ||
106 | * data through `AsyncStream` objects and the `decrypt` | ||
107 | * function | ||
108 | * | ||
109 | * @param {Uint8Array} encrypted the encrypted bytes | ||
110 | * @param {Uint32Array} key the bytes of the decryption key | ||
111 | * @param {Uint32Array} initVector the initialization vector (IV) to | ||
112 | * @param {Function} done the function to run when done | ||
113 | * @class Decrypter | ||
114 | */ | ||
115 | export class Decrypter { | ||
116 | constructor(encrypted, key, initVector, done) { | ||
117 | let step = Decrypter.STEP; | ||
118 | let encrypted32 = new Int32Array(encrypted.buffer); | ||
119 | let decrypted = new Uint8Array(encrypted.byteLength); | ||
120 | let i = 0; | ||
121 | |||
122 | this.asyncStream_ = new AsyncStream(); | ||
123 | |||
124 | // split up the encryption job and do the individual chunks asynchronously | ||
125 | this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), | ||
126 | key, | ||
127 | initVector, | ||
128 | decrypted)); | ||
129 | for (i = step; i < encrypted32.length; i += step) { | ||
130 | initVector = new Uint32Array([ntoh(encrypted32[i - 4]), | ||
131 | ntoh(encrypted32[i - 3]), | ||
132 | ntoh(encrypted32[i - 2]), | ||
133 | ntoh(encrypted32[i - 1])]); | ||
134 | this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), | ||
135 | key, | ||
136 | initVector, | ||
137 | decrypted)); | ||
138 | } | ||
139 | // invoke the done() callback when everything is finished | ||
140 | this.asyncStream_.push(function() { | ||
141 | // remove pkcs#7 padding from the decrypted bytes | ||
142 | done(null, unpad(decrypted)); | ||
143 | }); | ||
144 | } | ||
145 | |||
146 | /** | ||
147 | * a getter for step the maximum number of bytes to process at one time | ||
148 | * | ||
149 | * @return {Number} the value of step 32000 | ||
150 | */ | ||
151 | static get STEP() { | ||
152 | // 4 * 8000; | ||
153 | return 32000; | ||
154 | } | ||
155 | |||
156 | /** | ||
157 | * @private | ||
158 | */ | ||
159 | decryptChunk_(encrypted, key, initVector, decrypted) { | ||
160 | return function() { | ||
161 | let bytes = decrypt(encrypted, key, initVector); | ||
162 | |||
163 | decrypted.set(bytes, encrypted.byteOffset); | ||
164 | }; | ||
165 | } | ||
166 | } | ||
167 | |||
168 | export default { | ||
169 | Decrypter, | ||
170 | decrypt | ||
171 | }; |
src/decrypter/index.js
deleted
100644 → 0
1 | /** | ||
2 | * @file decrypter/index.js | ||
3 | * | ||
4 | * Index module to easily import the primary components of AES-128 | ||
5 | * decryption. Like this: | ||
6 | * | ||
7 | * ```js | ||
8 | * import {Decrypter, decrypt, AsyncStream} from './src/decrypter'; | ||
9 | * ``` | ||
10 | */ | ||
11 | import {decrypt, Decrypter} from './decrypter'; | ||
12 | import AsyncStream from './async-stream'; | ||
13 | |||
14 | export default { | ||
15 | decrypt, | ||
16 | Decrypter, | ||
17 | AsyncStream | ||
18 | }; |
... | @@ -5,7 +5,7 @@ import Ranges from './ranges'; | ... | @@ -5,7 +5,7 @@ import Ranges from './ranges'; |
5 | import {getMediaIndexForTime_ as getMediaIndexForTime, duration} from './playlist'; | 5 | import {getMediaIndexForTime_ as getMediaIndexForTime, duration} from './playlist'; |
6 | import videojs from 'video.js'; | 6 | import videojs from 'video.js'; |
7 | import SourceUpdater from './source-updater'; | 7 | import SourceUpdater from './source-updater'; |
8 | import {Decrypter} from './decrypter'; | 8 | import {Decrypter} from 'aes-decrypter'; |
9 | import Config from './config'; | 9 | import Config from './config'; |
10 | 10 | ||
11 | // in ms | 11 | // in ms | ... | ... |
... | @@ -8,7 +8,7 @@ import document from 'global/document'; | ... | @@ -8,7 +8,7 @@ import document from 'global/document'; |
8 | import PlaylistLoader from './playlist-loader'; | 8 | import PlaylistLoader from './playlist-loader'; |
9 | import Playlist from './playlist'; | 9 | import Playlist from './playlist'; |
10 | import xhrFactory from './xhr'; | 10 | import xhrFactory from './xhr'; |
11 | import {Decrypter, AsyncStream, decrypt} from './decrypter'; | 11 | import {Decrypter, AsyncStream, decrypt} from 'aes-decrypter'; |
12 | import utils from './bin-utils'; | 12 | import utils from './bin-utils'; |
13 | import {MediaSource, URL} from 'videojs-contrib-media-sources'; | 13 | import {MediaSource, URL} from 'videojs-contrib-media-sources'; |
14 | import m3u8 from 'm3u8-parser'; | 14 | import m3u8 from 'm3u8-parser'; | ... | ... |
test/decrypter.test.js
deleted
100644 → 0
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'; | ||
6 | |||
7 | // see docs/hlse.md for instructions on how test data was generated | ||
8 | const stringFromBytes = function(bytes) { | ||
9 | let result = ''; | ||
10 | |||
11 | for (let i = 0; i < bytes.length; i++) { | ||
12 | result += String.fromCharCode(bytes[i]); | ||
13 | } | ||
14 | return result; | ||
15 | }; | ||
16 | |||
17 | QUnit.module('Decryption'); | ||
18 | QUnit.test('decrypts a single AES-128 with PKCS7 block', function() { | ||
19 | let key = new Uint32Array([0, 0, 0, 0]); | ||
20 | let initVector = key; | ||
21 | // the string "howdy folks" encrypted | ||
22 | let encrypted = new Uint8Array([ | ||
23 | 0xce, 0x90, 0x97, 0xd0, | ||
24 | 0x08, 0x46, 0x4d, 0x18, | ||
25 | 0x4f, 0xae, 0x01, 0x1c, | ||
26 | 0x82, 0xa8, 0xf0, 0x67 | ||
27 | ]); | ||
28 | |||
29 | QUnit.deepEqual('howdy folks', | ||
30 | stringFromBytes(unpad(decrypt(encrypted, key, initVector))), | ||
31 | 'decrypted with a byte array key' | ||
32 | ); | ||
33 | }); | ||
34 | |||
35 | QUnit.test('decrypts multiple AES-128 blocks with CBC', function() { | ||
36 | let key = new Uint32Array([0, 0, 0, 0]); | ||
37 | let initVector = key; | ||
38 | // the string "0123456789abcdef01234" encrypted | ||
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([ | ||
76 | 0x14, 0xf5, 0xfe, 0x74, | ||
77 | 0x69, 0x66, 0xf2, 0x92, | ||
78 | 0x65, 0x1c, 0x22, 0x88, | ||
79 | 0xbb, 0xff, 0x46, 0x09, | ||
80 | |||
81 | 0x0b, 0xde, 0x5e, 0x71, | ||
82 | 0x77, 0x87, 0xeb, 0x84, | ||
83 | 0xa9, 0x54, 0xc2, 0x45, | ||
84 | 0xe9, 0x4e, 0x29, 0xb3 | ||
85 | ]); | ||
86 | |||
87 | QUnit.deepEqual('0123456789abcdef01234', | ||
88 | stringFromBytes(unpad(decrypt(cbcBlocks, key, initVector))), | ||
89 | 'decrypted multiple blocks'); | ||
90 | |||
91 | }); | ||
92 | |||
93 | QUnit.module('Incremental Processing', { | ||
94 | beforeEach() { | ||
95 | this.clock = sinon.useFakeTimers(); | ||
96 | }, | ||
97 | afterEach() { | ||
98 | this.clock.restore(); | ||
99 | } | ||
100 | }); | ||
101 | |||
102 | QUnit.test('executes a callback after a timeout', function() { | ||
103 | let asyncStream = new AsyncStream(); | ||
104 | let calls = ''; | ||
105 | |||
106 | asyncStream.push(function() { | ||
107 | calls += 'a'; | ||
108 | }); | ||
109 | |||
110 | this.clock.tick(asyncStream.delay); | ||
111 | QUnit.equal(calls, 'a', 'invoked the callback once'); | ||
112 | this.clock.tick(asyncStream.delay); | ||
113 | QUnit.equal(calls, 'a', 'only invoked the callback once'); | ||
114 | }); | ||
115 | |||
116 | QUnit.test('executes callback in series', function() { | ||
117 | let asyncStream = new AsyncStream(); | ||
118 | let calls = ''; | ||
119 | |||
120 | asyncStream.push(function() { | ||
121 | calls += 'a'; | ||
122 | }); | ||
123 | asyncStream.push(function() { | ||
124 | calls += 'b'; | ||
125 | }); | ||
126 | |||
127 | this.clock.tick(asyncStream.delay); | ||
128 | QUnit.equal(calls, 'a', 'invoked the first callback'); | ||
129 | this.clock.tick(asyncStream.delay); | ||
130 | QUnit.equal(calls, 'ab', 'invoked the second'); | ||
131 | }); | ||
132 | |||
133 | QUnit.module('Incremental Decryption', { | ||
134 | beforeEach() { | ||
135 | this.clock = sinon.useFakeTimers(); | ||
136 | }, | ||
137 | afterEach() { | ||
138 | this.clock.restore(); | ||
139 | } | ||
140 | }); | ||
141 | |||
142 | QUnit.test('asynchronously decrypts a 4-word block', function() { | ||
143 | let key = new Uint32Array([0, 0, 0, 0]); | ||
144 | let initVector = key; | ||
145 | // the string "howdy folks" encrypted | ||
146 | let encrypted = new Uint8Array([0xce, 0x90, 0x97, 0xd0, | ||
147 | 0x08, 0x46, 0x4d, 0x18, | ||
148 | 0x4f, 0xae, 0x01, 0x1c, | ||
149 | 0x82, 0xa8, 0xf0, 0x67]); | ||
150 | let decrypted; | ||
151 | let decrypter = new Decrypter(encrypted, | ||
152 | key, | ||
153 | initVector, | ||
154 | function(error, result) { | ||
155 | if (error) { | ||
156 | throw new Error(error); | ||
157 | } | ||
158 | decrypted = result; | ||
159 | }); | ||
160 | |||
161 | QUnit.ok(!decrypted, 'asynchronously decrypts'); | ||
162 | this.clock.tick(decrypter.asyncStream_.delay * 2); | ||
163 | |||
164 | QUnit.ok(decrypted, 'completed decryption'); | ||
165 | QUnit.deepEqual('howdy folks', | ||
166 | stringFromBytes(decrypted), | ||
167 | 'decrypts and unpads the result'); | ||
168 | }); | ||
169 | |||
170 | QUnit.test('breaks up input greater than the step value', function() { | ||
171 | let encrypted = new Int32Array(Decrypter.STEP + 4); | ||
172 | let done = false; | ||
173 | let decrypter = new Decrypter(encrypted, | ||
174 | new Uint32Array(4), | ||
175 | new Uint32Array(4), | ||
176 | function() { | ||
177 | done = true; | ||
178 | }); | ||
179 | |||
180 | this.clock.tick(decrypter.asyncStream_.delay * 2); | ||
181 | QUnit.ok(!done, 'not finished after two ticks'); | ||
182 | |||
183 | this.clock.tick(decrypter.asyncStream_.delay); | ||
184 | QUnit.ok(done, 'finished after the last chunk is decrypted'); | ||
185 | }); |
-
Please register or sign in to post a comment