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:
@@ -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 +
|
||||
|
||||
@@ -149,6 +149,10 @@ module.exports.ParseServerOptions = {
|
||||
action: parsers.booleanParser,
|
||||
default: false,
|
||||
},
|
||||
encryptionKey: {
|
||||
env: 'PARSE_SERVER_ENCRYPTION_KEY',
|
||||
help: 'Key for encrypting your files',
|
||||
},
|
||||
expireInactiveSessions: {
|
||||
env: 'PARSE_SERVER_EXPIRE_INACTIVE_SESSIONS',
|
||||
help: 'Sets wether we should expire the inactive sessions, defaults to true',
|
||||
|
||||
Reference in New Issue
Block a user