Skip to content

Commit 7f3ea3f

Browse files
authored
Add fileKey rotation to GridFSBucketAdapter (#6768)
* add fileKey encryption to GridFSBucketStorageAdapter * remove fileAdapter options from test spec * ensure promise doesn't fall through in getFileData * switch secretKey to fileKey * add fileKey rotation for GridFSBucketAdapter * improve catching decryption errors in testcases * add testcase for rotating key from oldKey to noKey leaving all files decrypted * removed fileKey from legacy test links. From the looks of the tests and the fileKey was appended to links. This key is now an encryption key * clean up code * make more consistant with FSAdapter * use encryptionKey instead of fileKey * Update ParseFile.spec.js revert
1 parent 1d038ee commit 7f3ea3f

File tree

3 files changed

+476
-38
lines changed

3 files changed

+476
-38
lines changed

spec/GridFSBucketStorageAdapter.spec.js

+354-9
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,365 @@ describe_only_db('mongo')('GridFSBucket and GridStore interop', () => {
3434
expect(gfsResult.toString('utf8')).toBe(originalString);
3535
});
3636

37-
it('an encypted file created in GridStore should be available in GridFS', async () => {
38-
const gsAdapter = new GridStoreAdapter(databaseURI);
39-
const gfsAdapter = new GridFSBucketAdapter(
37+
it('should save an encrypted file that can only be decrypted by a GridFS adapter with the encryptionKey', async () => {
38+
const unencryptedAdapter = new GridFSBucketAdapter(databaseURI);
39+
const encryptedAdapter = new GridFSBucketAdapter(
4040
databaseURI,
4141
{},
4242
'89E4AFF1-DFE4-4603-9574-BFA16BB446FD'
4343
);
44-
await expectMissingFile(gfsAdapter, 'myFileName');
44+
await expectMissingFile(encryptedAdapter, 'myFileName');
4545
const originalString = 'abcdefghi';
46-
await gfsAdapter.createFile('myFileName', originalString);
47-
const gsResult = await gsAdapter.getFileData('myFileName');
48-
expect(gsResult.toString('utf8')).not.toBe(originalString);
49-
const gfsResult = await gfsAdapter.getFileData('myFileName');
50-
expect(gfsResult.toString('utf8')).toBe(originalString);
46+
await encryptedAdapter.createFile('myFileName', originalString);
47+
const unencryptedResult = await unencryptedAdapter.getFileData(
48+
'myFileName'
49+
);
50+
expect(unencryptedResult.toString('utf8')).not.toBe(originalString);
51+
const encryptedResult = await encryptedAdapter.getFileData('myFileName');
52+
expect(encryptedResult.toString('utf8')).toBe(originalString);
53+
});
54+
55+
it('should rotate key of all unencrypted GridFS files to encrypted files', async () => {
56+
const unencryptedAdapter = new GridFSBucketAdapter(databaseURI);
57+
const encryptedAdapter = new GridFSBucketAdapter(
58+
databaseURI,
59+
{},
60+
'89E4AFF1-DFE4-4603-9574-BFA16BB446FD'
61+
);
62+
const fileName1 = 'file1.txt';
63+
const data1 = 'hello world';
64+
const fileName2 = 'file2.txt';
65+
const data2 = 'hello new world';
66+
//Store unecrypted files
67+
await unencryptedAdapter.createFile(fileName1, data1);
68+
const unencryptedResult1 = await unencryptedAdapter.getFileData(fileName1);
69+
expect(unencryptedResult1.toString('utf8')).toBe(data1);
70+
await unencryptedAdapter.createFile(fileName2, data2);
71+
const unencryptedResult2 = await unencryptedAdapter.getFileData(fileName2);
72+
expect(unencryptedResult2.toString('utf8')).toBe(data2);
73+
//Check if encrypted adapter can read data and make sure it's not the same as unEncrypted adapter
74+
const {
75+
rotated,
76+
notRotated,
77+
} = await encryptedAdapter.rotateEncryptionKey();
78+
expect(rotated.length).toEqual(2);
79+
expect(
80+
rotated.filter(function (value) {
81+
return value === fileName1;
82+
}).length
83+
).toEqual(1);
84+
expect(
85+
rotated.filter(function (value) {
86+
return value === fileName2;
87+
}).length
88+
).toEqual(1);
89+
expect(notRotated.length).toEqual(0);
90+
let result = await encryptedAdapter.getFileData(fileName1);
91+
expect(result instanceof Buffer).toBe(true);
92+
expect(result.toString('utf-8')).toEqual(data1);
93+
const encryptedData1 = await unencryptedAdapter.getFileData(fileName1);
94+
expect(encryptedData1.toString('utf-8')).not.toEqual(unencryptedResult1);
95+
result = await encryptedAdapter.getFileData(fileName2);
96+
expect(result instanceof Buffer).toBe(true);
97+
expect(result.toString('utf-8')).toEqual(data2);
98+
const encryptedData2 = await unencryptedAdapter.getFileData(fileName2);
99+
expect(encryptedData2.toString('utf-8')).not.toEqual(unencryptedResult2);
100+
});
101+
102+
it('should rotate key of all old encrypted GridFS files to encrypted files', async () => {
103+
const oldEncryptionKey = 'oldKeyThatILoved';
104+
const oldEncryptedAdapter = new GridFSBucketAdapter(
105+
databaseURI,
106+
{},
107+
oldEncryptionKey
108+
);
109+
const encryptedAdapter = new GridFSBucketAdapter(
110+
databaseURI,
111+
{},
112+
'newKeyThatILove'
113+
);
114+
const fileName1 = 'file1.txt';
115+
const data1 = 'hello world';
116+
const fileName2 = 'file2.txt';
117+
const data2 = 'hello new world';
118+
//Store unecrypted files
119+
await oldEncryptedAdapter.createFile(fileName1, data1);
120+
const oldEncryptedResult1 = await oldEncryptedAdapter.getFileData(
121+
fileName1
122+
);
123+
expect(oldEncryptedResult1.toString('utf8')).toBe(data1);
124+
await oldEncryptedAdapter.createFile(fileName2, data2);
125+
const oldEncryptedResult2 = await oldEncryptedAdapter.getFileData(
126+
fileName2
127+
);
128+
expect(oldEncryptedResult2.toString('utf8')).toBe(data2);
129+
//Check if encrypted adapter can read data and make sure it's not the same as unEncrypted adapter
130+
const { rotated, notRotated } = await encryptedAdapter.rotateEncryptionKey({
131+
oldKey: oldEncryptionKey,
132+
});
133+
expect(rotated.length).toEqual(2);
134+
expect(
135+
rotated.filter(function (value) {
136+
return value === fileName1;
137+
}).length
138+
).toEqual(1);
139+
expect(
140+
rotated.filter(function (value) {
141+
return value === fileName2;
142+
}).length
143+
).toEqual(1);
144+
expect(notRotated.length).toEqual(0);
145+
let result = await encryptedAdapter.getFileData(fileName1);
146+
expect(result instanceof Buffer).toBe(true);
147+
expect(result.toString('utf-8')).toEqual(data1);
148+
let decryptionError1;
149+
let encryptedData1;
150+
try {
151+
encryptedData1 = await oldEncryptedAdapter.getFileData(fileName1);
152+
} catch (err) {
153+
decryptionError1 = err;
154+
}
155+
expect(decryptionError1).toMatch('Error');
156+
expect(encryptedData1).toBeUndefined();
157+
result = await encryptedAdapter.getFileData(fileName2);
158+
expect(result instanceof Buffer).toBe(true);
159+
expect(result.toString('utf-8')).toEqual(data2);
160+
let decryptionError2;
161+
let encryptedData2;
162+
try {
163+
encryptedData2 = await oldEncryptedAdapter.getFileData(fileName2);
164+
} catch (err) {
165+
decryptionError2 = err;
166+
}
167+
expect(decryptionError2).toMatch('Error');
168+
expect(encryptedData2).toBeUndefined();
169+
});
170+
171+
it('should rotate key of all old encrypted GridFS files to unencrypted files', async () => {
172+
const oldEncryptionKey = 'oldKeyThatILoved';
173+
const oldEncryptedAdapter = new GridFSBucketAdapter(
174+
databaseURI,
175+
{},
176+
oldEncryptionKey
177+
);
178+
const unEncryptedAdapter = new GridFSBucketAdapter(databaseURI);
179+
const fileName1 = 'file1.txt';
180+
const data1 = 'hello world';
181+
const fileName2 = 'file2.txt';
182+
const data2 = 'hello new world';
183+
//Store unecrypted files
184+
await oldEncryptedAdapter.createFile(fileName1, data1);
185+
const oldEncryptedResult1 = await oldEncryptedAdapter.getFileData(
186+
fileName1
187+
);
188+
expect(oldEncryptedResult1.toString('utf8')).toBe(data1);
189+
await oldEncryptedAdapter.createFile(fileName2, data2);
190+
const oldEncryptedResult2 = await oldEncryptedAdapter.getFileData(
191+
fileName2
192+
);
193+
expect(oldEncryptedResult2.toString('utf8')).toBe(data2);
194+
//Check if unEncrypted adapter can read data and make sure it's not the same as oldEncrypted adapter
195+
const {
196+
rotated,
197+
notRotated,
198+
} = await unEncryptedAdapter.rotateEncryptionKey({
199+
oldKey: oldEncryptionKey,
200+
});
201+
expect(rotated.length).toEqual(2);
202+
expect(
203+
rotated.filter(function (value) {
204+
return value === fileName1;
205+
}).length
206+
).toEqual(1);
207+
expect(
208+
rotated.filter(function (value) {
209+
return value === fileName2;
210+
}).length
211+
).toEqual(1);
212+
expect(notRotated.length).toEqual(0);
213+
let result = await unEncryptedAdapter.getFileData(fileName1);
214+
expect(result instanceof Buffer).toBe(true);
215+
expect(result.toString('utf-8')).toEqual(data1);
216+
let decryptionError1;
217+
let encryptedData1;
218+
try {
219+
encryptedData1 = await oldEncryptedAdapter.getFileData(fileName1);
220+
} catch (err) {
221+
decryptionError1 = err;
222+
}
223+
expect(decryptionError1).toMatch('Error');
224+
expect(encryptedData1).toBeUndefined();
225+
result = await unEncryptedAdapter.getFileData(fileName2);
226+
expect(result instanceof Buffer).toBe(true);
227+
expect(result.toString('utf-8')).toEqual(data2);
228+
let decryptionError2;
229+
let encryptedData2;
230+
try {
231+
encryptedData2 = await oldEncryptedAdapter.getFileData(fileName2);
232+
} catch (err) {
233+
decryptionError2 = err;
234+
}
235+
expect(decryptionError2).toMatch('Error');
236+
expect(encryptedData2).toBeUndefined();
237+
});
238+
239+
it('should only encrypt specified fileNames', async () => {
240+
const oldEncryptionKey = 'oldKeyThatILoved';
241+
const oldEncryptedAdapter = new GridFSBucketAdapter(
242+
databaseURI,
243+
{},
244+
oldEncryptionKey
245+
);
246+
const encryptedAdapter = new GridFSBucketAdapter(
247+
databaseURI,
248+
{},
249+
'newKeyThatILove'
250+
);
251+
const unEncryptedAdapter = new GridFSBucketAdapter(databaseURI);
252+
const fileName1 = 'file1.txt';
253+
const data1 = 'hello world';
254+
const fileName2 = 'file2.txt';
255+
const data2 = 'hello new world';
256+
//Store unecrypted files
257+
await oldEncryptedAdapter.createFile(fileName1, data1);
258+
const oldEncryptedResult1 = await oldEncryptedAdapter.getFileData(
259+
fileName1
260+
);
261+
expect(oldEncryptedResult1.toString('utf8')).toBe(data1);
262+
await oldEncryptedAdapter.createFile(fileName2, data2);
263+
const oldEncryptedResult2 = await oldEncryptedAdapter.getFileData(
264+
fileName2
265+
);
266+
expect(oldEncryptedResult2.toString('utf8')).toBe(data2);
267+
//Inject unecrypted file to see if causes an issue
268+
const fileName3 = 'file3.txt';
269+
const data3 = 'hello past world';
270+
await unEncryptedAdapter.createFile(fileName3, data3, 'text/utf8');
271+
//Check if encrypted adapter can read data and make sure it's not the same as unEncrypted adapter
272+
const { rotated, notRotated } = await encryptedAdapter.rotateEncryptionKey({
273+
oldKey: oldEncryptionKey,
274+
fileNames: [fileName1, fileName2],
275+
});
276+
expect(rotated.length).toEqual(2);
277+
expect(
278+
rotated.filter(function (value) {
279+
return value === fileName1;
280+
}).length
281+
).toEqual(1);
282+
expect(
283+
rotated.filter(function (value) {
284+
return value === fileName2;
285+
}).length
286+
).toEqual(1);
287+
expect(notRotated.length).toEqual(0);
288+
expect(
289+
rotated.filter(function (value) {
290+
return value === fileName3;
291+
}).length
292+
).toEqual(0);
293+
let result = await encryptedAdapter.getFileData(fileName1);
294+
expect(result instanceof Buffer).toBe(true);
295+
expect(result.toString('utf-8')).toEqual(data1);
296+
let decryptionError1;
297+
let encryptedData1;
298+
try {
299+
encryptedData1 = await oldEncryptedAdapter.getFileData(fileName1);
300+
} catch (err) {
301+
decryptionError1 = err;
302+
}
303+
expect(decryptionError1).toMatch('Error');
304+
expect(encryptedData1).toBeUndefined();
305+
result = await encryptedAdapter.getFileData(fileName2);
306+
expect(result instanceof Buffer).toBe(true);
307+
expect(result.toString('utf-8')).toEqual(data2);
308+
let decryptionError2;
309+
let encryptedData2;
310+
try {
311+
encryptedData2 = await oldEncryptedAdapter.getFileData(fileName2);
312+
} catch (err) {
313+
decryptionError2 = err;
314+
}
315+
expect(decryptionError2).toMatch('Error');
316+
expect(encryptedData2).toBeUndefined();
317+
});
318+
319+
it("should return fileNames of those it can't encrypt with the new key", async () => {
320+
const oldEncryptionKey = 'oldKeyThatILoved';
321+
const oldEncryptedAdapter = new GridFSBucketAdapter(
322+
databaseURI,
323+
{},
324+
oldEncryptionKey
325+
);
326+
const encryptedAdapter = new GridFSBucketAdapter(
327+
databaseURI,
328+
{},
329+
'newKeyThatILove'
330+
);
331+
const unEncryptedAdapter = new GridFSBucketAdapter(databaseURI);
332+
const fileName1 = 'file1.txt';
333+
const data1 = 'hello world';
334+
const fileName2 = 'file2.txt';
335+
const data2 = 'hello new world';
336+
//Store unecrypted files
337+
await oldEncryptedAdapter.createFile(fileName1, data1);
338+
const oldEncryptedResult1 = await oldEncryptedAdapter.getFileData(
339+
fileName1
340+
);
341+
expect(oldEncryptedResult1.toString('utf8')).toBe(data1);
342+
await oldEncryptedAdapter.createFile(fileName2, data2);
343+
const oldEncryptedResult2 = await oldEncryptedAdapter.getFileData(
344+
fileName2
345+
);
346+
expect(oldEncryptedResult2.toString('utf8')).toBe(data2);
347+
//Inject unecrypted file to see if causes an issue
348+
const fileName3 = 'file3.txt';
349+
const data3 = 'hello past world';
350+
await unEncryptedAdapter.createFile(fileName3, data3, 'text/utf8');
351+
//Check if encrypted adapter can read data and make sure it's not the same as unEncrypted adapter
352+
const { rotated, notRotated } = await encryptedAdapter.rotateEncryptionKey({
353+
oldKey: oldEncryptionKey,
354+
});
355+
expect(rotated.length).toEqual(2);
356+
expect(
357+
rotated.filter(function (value) {
358+
return value === fileName1;
359+
}).length
360+
).toEqual(1);
361+
expect(
362+
rotated.filter(function (value) {
363+
return value === fileName2;
364+
}).length
365+
).toEqual(1);
366+
expect(notRotated.length).toEqual(1);
367+
expect(
368+
notRotated.filter(function (value) {
369+
return value === fileName3;
370+
}).length
371+
).toEqual(1);
372+
let result = await encryptedAdapter.getFileData(fileName1);
373+
expect(result instanceof Buffer).toBe(true);
374+
expect(result.toString('utf-8')).toEqual(data1);
375+
let decryptionError1;
376+
let encryptedData1;
377+
try {
378+
encryptedData1 = await oldEncryptedAdapter.getFileData(fileName1);
379+
} catch (err) {
380+
decryptionError1 = err;
381+
}
382+
expect(decryptionError1).toMatch('Error');
383+
expect(encryptedData1).toBeUndefined();
384+
result = await encryptedAdapter.getFileData(fileName2);
385+
expect(result instanceof Buffer).toBe(true);
386+
expect(result.toString('utf-8')).toEqual(data2);
387+
let decryptionError2;
388+
let encryptedData2;
389+
try {
390+
encryptedData2 = await oldEncryptedAdapter.getFileData(fileName2);
391+
} catch (err) {
392+
decryptionError2 = err;
393+
}
394+
expect(decryptionError2).toMatch('Error');
395+
expect(encryptedData2).toBeUndefined();
51396
});
52397

53398
it('should save metadata', async () => {

0 commit comments

Comments
 (0)