support providing multiple secrets, always encrypt with first, attempt decryption...
[squeep-mystery-box] / test / lib / mystery-box.js
1 /* eslint-env mocha */
2 /* eslint-disable capitalized-comments */
3 'use strict';
4
5 const assert = require('assert');
6 const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require
7 const MysteryBox = require('../../lib/mystery-box');
8 const stubLogger = require('../stub-logger');
9
10 describe('MysteryBox', function () {
11 const noExpectedException = 'did not get expected exception';
12 let mb, options, object;
13 beforeEach(function () {
14 options = {
15 encryptionSecret: 'this is not a very good secret',
16 };
17 });
18 afterEach(function () {
19 sinon.restore();
20 });
21
22 describe('constructor', function () {
23 it('needs a secret', async function () {
24 options = {};
25 try {
26 mb = new MysteryBox(stubLogger, options);
27 assert.fail(noExpectedException);
28 } catch (e) {
29 assert.strictEqual(e.message, 'missing encryption secret', noExpectedException);
30 }
31 });
32
33 it('accepts multiple secrets', async function () {
34 this.slow(500);
35 options = {
36 encryptionSecret: ['first poor secret', 'second poor secret'],
37 };
38 mb = new MysteryBox(stubLogger, options);
39 object = {
40 foo: 'bar',
41 baz: 'quux',
42 flarp: 13,
43 };
44 const encryptedResult = await mb.pack(object);
45 const decryptedResult = await mb.unpack(encryptedResult);
46 assert.deepStrictEqual(decryptedResult, object);
47
48 });
49
50 it('covers options', function () {
51 try {
52 mb = new MysteryBox(stubLogger);
53 assert.fail(noExpectedException);
54 } catch (e) {
55 assert.strictEqual(e.message, 'missing encryption secret', noExpectedException);
56 }
57 });
58
59 it('covers bad flags', function () {
60 options.defaultFlags = 300;
61 try {
62 mb = new MysteryBox(stubLogger, options);
63 assert.fail(noExpectedException);
64 } catch (e) {
65 assert(e instanceof RangeError, noExpectedException);
66 }
67 });
68
69 it('covers missing ciphers', function () {
70 sinon.stub(MysteryBox._test.crypto, 'getCiphers').returns(['rot13']);
71 try {
72 mb = new MysteryBox(stubLogger, options);
73 assert.fail(noExpectedException);
74 } catch (e) {
75 assert.strictEqual(e.message, 'no supported versions available', noExpectedException);
76 }
77 });
78 }); // constructor
79
80 describe('pack, unpack', function () {
81 beforeEach(function () {
82 mb = new MysteryBox(stubLogger, options);
83 });
84
85 it('covers packing unsupported version', async function () {
86 try {
87 await mb.pack({}, 0);
88 assert.fail(noExpectedException);
89 } catch (e) {
90 assert(e instanceof RangeError, noExpectedException);
91 }
92 });
93
94 it('covers unpacking unsupported version', async function () {
95 const badBuffer = Buffer.alloc(128);
96 badBuffer.writeUInt8(0, 0); // No such thing as version 0
97 const badPayload = badBuffer.toString('base64');
98 try {
99 await mb.unpack(badPayload);
100 assert.fail(noExpectedException);
101 } catch (e) {
102 assert(e instanceof RangeError, noExpectedException);
103 }
104 });
105
106 it('encrypts and decrypts default version', async function () {
107 this.slow(500);
108 object = {
109 foo: 'bar',
110 baz: 'quux',
111 flarp: 13,
112 };
113 const encryptedResult = await mb.pack(object);
114 const decryptedResult = await mb.unpack(encryptedResult);
115 assert.deepStrictEqual(decryptedResult, object);
116 });
117
118 it('encrypts and decrypts default version, buffer contents', async function () {
119 this.slow(500);
120 object = Buffer.from('a fine little buffer');
121 const encryptedResult = await mb.pack(object);
122 const decryptedResult = await mb.unpack(encryptedResult);
123 assert.deepStrictEqual(decryptedResult, object);
124 });
125
126 it('decrypts secondary (older) secret', async function () {
127 this.slow(500);
128 const oldmb = new MysteryBox(stubLogger, { encryptionSecret: 'old secret' });
129 const newmb = new MysteryBox(stubLogger, { encryptionSecret: ['new secret', 'old secret'] });
130 object = {
131 foo: 'bar',
132 baz: 'quux',
133 flarp: 13,
134 };
135 const oldEncrypted = await oldmb.pack(object);
136 const newDecrypted = await newmb.unpack(oldEncrypted);
137 assert.deepStrictEqual(newDecrypted, object);
138 });
139
140 it('fails to decrypt invalid secret', async function () {
141 this.slow(500);
142 const oldmb = new MysteryBox(stubLogger, { encryptionSecret: 'very old secret' });
143 const newmb = new MysteryBox(stubLogger, { encryptionSecret: ['new secret', 'old secret'] });
144 object = {
145 foo: 'bar',
146 baz: 'quux',
147 flarp: 13,
148 };
149 const oldEncrypted = await oldmb.pack(object);
150 try {
151 await newmb.unpack(oldEncrypted);
152 assert.fail(noExpectedException);
153 } catch (e) {
154 assert(e instanceof Error);
155 }
156 });
157
158 it('encrypts and decrypts all available versions +brotli', async function () {
159 Object.keys(mb.versionParameters).map((v) => Number(v)).forEach(async (version) => {
160 object = {
161 foo: 'bar',
162 baz: 'quux',
163 flarp: 13,
164 };
165 const encryptedResult = await mb.pack(object, version, 0x00);
166 const decryptedResult = await mb.unpack(encryptedResult);
167 assert.deepStrictEqual(decryptedResult, object, `${version} results not symmetric`);
168 });
169 });
170
171 it('encrypts and decrypts all available versions +flate', async function () {
172 Object.keys(mb.versionParameters).map((v) => Number(v)).forEach(async (version) => {
173 object = {
174 foo: 'bar',
175 baz: 'quux',
176 flarp: 13,
177 };
178 const encryptedResult = await mb.pack(object, version, 0x01);
179 const decryptedResult = await mb.unpack(encryptedResult);
180 assert.deepStrictEqual(decryptedResult, object, `${version} results not symmetric`);
181 });
182 });
183
184 it('handles large object +brotli', async function () {
185 this.timeout(5000);
186 this.slow(2000);
187 const firstChar = 32, lastChar = 126;
188 const rnd = () => {
189 return Math.floor(firstChar + (lastChar - firstChar + 1) * Math.random());
190 }
191 object = {
192 longProperty: 'x'.repeat(384 * 1024).split('').map(() => String.fromCharCode(rnd())).join(''),
193 };
194 const encryptedResult = await mb.pack(object, mb.bestVersion, 0x00);
195 const decryptedResult = await mb.unpack(encryptedResult);
196 assert.deepStrictEqual(decryptedResult, object);
197 });
198
199 it('handles large object +flate', async function () {
200 this.timeout(5000);
201 this.slow(2000);
202 const firstChar = 32, lastChar = 126;
203 const rnd = () => {
204 return Math.floor(firstChar + (lastChar - firstChar + 1) * Math.random());
205 }
206 object = {
207 longProperty: 'x'.repeat(384 * 1024).split('').map(() => String.fromCharCode(rnd())).join(''),
208 };
209 const encryptedResult = await mb.pack(object, mb.bestVersion, 0x01);
210 const decryptedResult = await mb.unpack(encryptedResult);
211 assert.deepStrictEqual(decryptedResult, object);
212 });
213
214 it('handles undefined', async function () {
215 try {
216 await mb.unpack();
217 assert.fail(noExpectedException);
218 } catch (e) {
219 assert(e instanceof RangeError, noExpectedException);
220 }
221 });
222
223 it('handles incomplete', async function () {
224 this.slow(500);
225 const encryptedResult = await mb.pack({ foo: 'bar' });
226 try {
227 await mb.unpack(encryptedResult.slice(0, 6));
228 assert.fail(noExpectedException);
229 } catch (e) {
230 assert(e instanceof RangeError, noExpectedException);
231 }
232 });
233
234 }); // pack, unpack
235
236 }); // MysteryBox