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
This commit is contained in:
Corey
2020-10-26 01:17:43 -04:00
committed by GitHub
parent 1d038ee58d
commit 7f3ea3fe80
3 changed files with 476 additions and 38 deletions

View File

@@ -21,16 +21,16 @@ export class GridFSBucketAdapter extends FilesAdapter {
constructor(
mongoDatabaseURI = defaults.DefaultMongoURI,
mongoOptions = {},
fileKey = undefined
encryptionKey = undefined
) {
super();
this._databaseURI = mongoDatabaseURI;
this._algorithm = 'aes-256-gcm';
this._fileKey =
fileKey !== undefined
this._encryptionKey =
encryptionKey !== undefined
? crypto
.createHash('sha256')
.update(String(fileKey))
.update(String(encryptionKey))
.digest('base64')
.substr(0, 32)
: null;
@@ -65,16 +65,26 @@ export class GridFSBucketAdapter extends FilesAdapter {
const stream = await bucket.openUploadStream(filename, {
metadata: options.metadata,
});
if (this._fileKey !== null) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(this._algorithm, this._fileKey, iv);
const encryptedResult = Buffer.concat([
cipher.update(data),
cipher.final(),
iv,
cipher.getAuthTag(),
]);
await stream.write(encryptedResult);
if (this._encryptionKey !== null) {
try {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(
this._algorithm,
this._encryptionKey,
iv
);
const encryptedResult = Buffer.concat([
cipher.update(data),
cipher.final(),
iv,
cipher.getAuthTag(),
]);
await stream.write(encryptedResult);
} catch (err) {
return new Promise((resolve, reject) => {
return reject(err);
});
}
} else {
await stream.write(data);
}
@@ -109,21 +119,27 @@ export class GridFSBucketAdapter extends FilesAdapter {
});
stream.on('end', () => {
const data = Buffer.concat(chunks);
if (this._fileKey !== null) {
const authTagLocation = data.length - 16;
const ivLocation = data.length - 32;
const authTag = data.slice(authTagLocation);
const iv = data.slice(ivLocation, authTagLocation);
const encrypted = data.slice(0, ivLocation);
const decipher = crypto.createDecipheriv(
this._algorithm,
this._fileKey,
iv
);
decipher.setAuthTag(authTag);
return resolve(
Buffer.concat([decipher.update(encrypted), decipher.final()])
);
if (this._encryptionKey !== null) {
try {
const authTagLocation = data.length - 16;
const ivLocation = data.length - 32;
const authTag = data.slice(authTagLocation);
const iv = data.slice(ivLocation, authTagLocation);
const encrypted = data.slice(0, ivLocation);
const decipher = crypto.createDecipheriv(
this._algorithm,
this._encryptionKey,
iv
);
decipher.setAuthTag(authTag);
const decrypted = Buffer.concat([
decipher.update(encrypted),
decipher.final(),
]);
return resolve(decrypted);
} catch (err) {
return reject(err);
}
}
resolve(data);
});
@@ -133,6 +149,79 @@ export class GridFSBucketAdapter extends FilesAdapter {
});
}
async rotateEncryptionKey(options = {}) {
var fileNames = [];
var oldKeyFileAdapter = {};
const bucket = await this._getBucket();
if (options.oldKey !== undefined) {
oldKeyFileAdapter = new GridFSBucketAdapter(
this._databaseURI,
this._mongoOptions,
options.oldKey
);
} else {
oldKeyFileAdapter = new GridFSBucketAdapter(
this._databaseURI,
this._mongoOptions
);
}
if (options.fileNames !== undefined) {
fileNames = options.fileNames;
} else {
const fileNamesIterator = await bucket.find().toArray();
fileNamesIterator.forEach(file => {
fileNames.push(file.filename);
});
}
return new Promise(resolve => {
var fileNamesNotRotated = fileNames;
var fileNamesRotated = [];
var fileNameTotal = fileNames.length;
var fileNameIndex = 0;
fileNames.forEach(fileName => {
oldKeyFileAdapter
.getFileData(fileName)
.then(plainTextData => {
//Overwrite file with data encrypted with new key
this.createFile(fileName, plainTextData)
.then(() => {
fileNamesRotated.push(fileName);
fileNamesNotRotated = fileNamesNotRotated.filter(function (
value
) {
return value !== fileName;
});
fileNameIndex += 1;
if (fileNameIndex == fileNameTotal) {
resolve({
rotated: fileNamesRotated,
notRotated: fileNamesNotRotated,
});
}
})
.catch(() => {
fileNameIndex += 1;
if (fileNameIndex == fileNameTotal) {
resolve({
rotated: fileNamesRotated,
notRotated: fileNamesNotRotated,
});
}
});
})
.catch(() => {
fileNameIndex += 1;
if (fileNameIndex == fileNameTotal) {
resolve({
rotated: fileNamesRotated,
notRotated: fileNamesNotRotated,
});
}
});
});
});
}
getFileLocation(config, filename) {
return (
config.mount +