Skip to content

Switch from fileKey to encryptionKey #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Oct 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ pids
*.pid
*.seed

#Test file creation directory
files

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

Expand Down
29 changes: 14 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,21 @@ When using parse-server-fs-adapter across multiple parse-server instances it's i
"filesAdapter": {
"module": "@parse/fs-files-adapter",
"options": {
"filesSubDirectory": "my/files/folder", // optional
"fileKey": "someKey" //optional, but mandatory if you want to encrypt files
"filesSubDirectory": "my/files/folder", // optional, defaults to ./files
"encryptionKey": "someKey" //optional, but mandatory if you want to encrypt files
}
}
}
```

### Passing as an instance
***Notice: If used with parse-server versions <= 4.2.0, DO NOT PASS in `PARSE_SERVER_FILE_KEY` or `fileKey` from parse-server. Instead pass your key directly to `FSFilesAdapter` using your own environment variable or hardcoding the string. parse-server versions > 4.2.0 can pass in `PARSE_SERVER_FILE_KEY` or `fileKey`.***

```javascript
var FSFilesAdapter = require('@parse/fs-files-adapter');

var fsAdapter = new FSFilesAdapter({
"filesSubDirectory": "my/files/folder", // optional
"fileKey": "someKey" //optional, but mandatory if you want to encrypt files
"filesSubDirectory": "my/files/folder", // optional, defaults to ./files
"encryptionKey": "someKey" //optional, but mandatory if you want to encrypt files
});

var api = new ParseServer({
Expand All @@ -49,16 +48,16 @@ var api = new ParseServer({
})
```

### Rotating to a new fileKey
Periodically you may want to rotate your fileKey for security reasons. When this is the case, you can start up a development parse-server that has the same configuration as your production server. In the development server, initialize the file adapter with the new key and do the following in your `index.js`:
### Rotating to a new encryptionKey
Periodically you may want to rotate your encryptionKey for security reasons. When this is the case, you can start up a development parse-server that has the same configuration as your production server. In the development server, initialize the file adapter with the new key and do the following in your `index.js`:

#### Files were previously unencrypted and you want to encrypt
```javascript
var FSFilesAdapter = require('@parse/fs-files-adapter');

var fsAdapter = new FSFilesAdapter({
"filesSubDirectory": "my/files/folder", // optional
"fileKey": "newKey" //Use the newKey
"filesSubDirectory": "my/files/folder", // optional, defaults to ./files
"encryptionKey": "newKey" //Use the newKey
});

var api = new ParseServer({
Expand All @@ -69,28 +68,28 @@ var api = new ParseServer({

//This can take awhile depending on how many files and how larger they are. It will attempt to rotate the key of all files in your filesSubDirectory
//It is not recommended to do this on the production server, deploy a development server to complete the process.
const {rotated, notRotated} = await api.filesAdapter.rotateFileKey();
const {rotated, notRotated} = await api.filesAdapter.rotateEncryptionKey();
console.log('Files rotated to newKey: ' + rotated);
console.log('Files that couldn't be rotated to newKey: ' + notRotated);
```

After successfully rotating your key, you should change the `fileKey` to `newKey` on your production server and then restart the server.
After successfully rotating your key, you should change the `encryptionKey` to `newKey` on your production server and then restart the server.


#### Files were previously encrypted with `oldKey` and you want to encrypt with `newKey`
The same process as above, but pass in your `oldKey` to `rotateFileKey()`.
The same process as above, but pass in your `oldKey` to `rotateEncryptionKey()`.
```javascript
//This can take awhile depending on how many files and how larger they are. It will attempt to rotate the key of all files in your filesSubDirectory
const {rotated, notRotated} = await api.filesAdapter.rotateFileKey({oldKey: oldKey});
const {rotated, notRotated} = await api.filesAdapter.rotateEncryptionKey({oldKey: oldKey});
console.log('Files rotated to newKey: ' + rotated);
console.log('Files that couldn't be rotated to newKey: ' + notRotated);
```

#### Only rotate a select list of files that were previously encrypted with `oldKey` and you want to encrypt with `newKey`
This is useful if for some reason there errors and some of the files werent rotated and returned in `notRotated`. The same process as above, but pass in your `oldKey` along with the array of `fileNames` to `rotateFileKey()`.
This is useful if for some reason there errors and some of the files werent rotated and returned in `notRotated`. The same process as above, but pass in your `oldKey` along with the array of `fileNames` to `rotateEncryptionKey()`.
```javascript
//This can take awhile depending on how many files and how larger they are. It will attempt to rotate the key of all files in your filesSubDirectory
const {rotated, notRotated} = await api.filesAdapter.rotateFileKey({oldKey: oldKey, fileNames: ["fileName1.png","fileName2.png"]});
const {rotated, notRotated} = await api.filesAdapter.rotateEncryptionKey({oldKey: oldKey, fileNames: ["fileName1.png","fileName2.png"]});
console.log('Files rotated to newKey: ' + rotated);
console.log('Files that couldn't be rotated to newKey: ' + notRotated);
```
18 changes: 9 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ const algorithm = 'aes-256-gcm';

function FileSystemAdapter(options) {
options = options || {};
this._fileKey = null;
this._encryptionKey = null;

if (options.fileKey !== undefined){
this._fileKey = crypto.createHash('sha256').update(String(options.fileKey)).digest('base64').substr(0, 32);
if (options.encryptionKey !== undefined){
this._encryptionKey = crypto.createHash('sha256').update(String(options.encryptionKey)).digest('base64').substr(0, 32);
}
let filesSubDirectory = options.filesSubDirectory || '';
this._filesDir = filesSubDirectory;
Expand All @@ -30,11 +30,11 @@ FileSystemAdapter.prototype.createFile = function(filename, data) {
const stream = fs.createWriteStream(filepath);
return new Promise((resolve, reject) => {
try{
if(this._fileKey !== null){
if(this._encryptionKey !== null){
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(
algorithm,
this._fileKey,
this._encryptionKey,
iv
);
const encryptedResult = Buffer.concat([
Expand Down Expand Up @@ -96,14 +96,14 @@ FileSystemAdapter.prototype.getFileData = function(filename) {
});
stream.on('end', () => {
const data = Buffer.concat(chunks);
if(this._fileKey !== null){
if(this._encryptionKey !== 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);
try{
const decipher = crypto.createDecipheriv(algorithm, this._fileKey, iv);
const decipher = crypto.createDecipheriv(algorithm, this._encryptionKey, iv);
decipher.setAuthTag(authTag);
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
return resolve(decrypted);
Expand All @@ -119,12 +119,12 @@ FileSystemAdapter.prototype.getFileData = function(filename) {
});
}

FileSystemAdapter.prototype.rotateFileKey = function(options = {}) {
FileSystemAdapter.prototype.rotateEncryptionKey = function(options = {}) {
const applicationDir = this._getApplicationDir();
var fileNames = [];
var oldKeyFileAdapter = {};
if (options.oldKey !== undefined) {
oldKeyFileAdapter = new FileSystemAdapter({filesSubDirectory: this._filesDir, fileKey: options.oldKey});
oldKeyFileAdapter = new FileSystemAdapter({filesSubDirectory: this._filesDir, encryptionKey: options.oldKey});
}else{
oldKeyFileAdapter = new FileSystemAdapter({filesSubDirectory: this._filesDir});
}
Expand Down
77 changes: 58 additions & 19 deletions spec/secureFiles.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,49 @@ describe('File encryption tests', () => {
})
});

it("should save encrypted file", async function(done) {
it('should create file location based on config', async function(done) {
var fsAdapter = new FileSystemAdapter();
var config = {mount: '/parse', applicationId: 'yolo'}
let location = fsAdapter.getFileLocation(config, 'hello.txt')
expect(location).toBe('/parse/files/yolo/hello.txt');
done()
}, 5000)

it("should save encrypted file in default directory", async function(done) {
var adapter = new FileSystemAdapter({
encryptionKey: '89E4AFF1-DFE4-4603-9574-BFA16BB446FD'
});
var filename = 'file2.txt';
const filePath = 'files/'+filename;
await adapter.createFile(filename, "hello world", 'text/utf8');
const result = await adapter.getFileData(filename);
expect(result instanceof Buffer).toBe(true);
expect(result.toString('utf-8')).toEqual("hello world");
const data = fs.readFileSync(filePath);
expect(data.toString('utf-8')).not.toEqual("hello world");
done()
}, 5000);

it("should save encrypted file in specified directory", async function(done) {
var adapter = new FileSystemAdapter({
filesSubDirectory: directory,
fileKey: '89E4AFF1-DFE4-4603-9574-BFA16BB446FD'
encryptionKey: '89E4AFF1-DFE4-4603-9574-BFA16BB446FD'
});
var filename = 'file2.txt';
const filePath = 'files/'+directory+'/'+filename;
await adapter.createFile(filename, "hello world", 'text/utf8');
const result = await adapter.getFileData(filename);
expect(result instanceof Buffer).toBe(true);
expect(result.toString('utf-8')).toEqual("hello world");
const data = fs.readFileSync(filePath);
expect(data.toString('utf-8')).not.toEqual("hello world");
done()
}, 5000);

it("should save encrypted file in specified directory when directory starts with /", async function(done) {
var adapter = new FileSystemAdapter({
filesSubDirectory: '/sub1/sub2',
encryptionKey: '89E4AFF1-DFE4-4603-9574-BFA16BB446FD'
});
var filename = 'file2.txt';
const filePath = 'files/'+directory+'/'+filename;
Expand All @@ -41,7 +80,7 @@ describe('File encryption tests', () => {
});
const encryptedAdapter = new FileSystemAdapter({
filesSubDirectory: directory,
fileKey: '89E4AFF1-DFE4-4603-9574-BFA16BB446FD'
encryptionKey: '89E4AFF1-DFE4-4603-9574-BFA16BB446FD'
});
const fileName1 = 'file1.txt';
const data1 = "hello world";
Expand All @@ -61,7 +100,7 @@ describe('File encryption tests', () => {
expect(result.toString('utf-8')).toEqual(data2);
const unEncryptedData2 = fs.readFileSync(filePath2);
//Check if encrypted adapter can read data and make sure it's not the same as unEncrypted adapter
const {rotated, notRotated} = await encryptedAdapter.rotateFileKey();
const {rotated, notRotated} = await encryptedAdapter.rotateEncryptionKey();
expect(rotated.length).toEqual(2);
expect(rotated.filter(function(value){ return value === fileName1;}).length).toEqual(1);
expect(rotated.filter(function(value){ return value === fileName2;}).length).toEqual(1);
Expand All @@ -80,14 +119,14 @@ describe('File encryption tests', () => {
}, 5000);

it("should rotate key of all old encrypted files to files encrypted with a new key", async function(done) {
const oldFileKey = 'oldKeyThatILoved';
const oldEncryptionKey = 'oldKeyThatILoved';
const oldEncryptedAdapter = new FileSystemAdapter({
filesSubDirectory: directory,
fileKey: oldFileKey
encryptionKey: oldEncryptionKey
});
const encryptedAdapter = new FileSystemAdapter({
filesSubDirectory: directory,
fileKey: 'newKeyThatILove'
encryptionKey: 'newKeyThatILove'
});
const fileName1 = 'file1.txt';
const data1 = "hello world";
Expand All @@ -107,7 +146,7 @@ describe('File encryption tests', () => {
expect(result.toString('utf-8')).toEqual(data2);
const oldEncryptedData2 = fs.readFileSync(filePath2);
//Check if encrypted adapter can read data and make sure it's not the same as unEncrypted adapter
const {rotated, notRotated} = await encryptedAdapter.rotateFileKey({oldKey: oldFileKey});
const {rotated, notRotated} = await encryptedAdapter.rotateEncryptionKey({oldKey: oldEncryptionKey});
expect(rotated.length).toEqual(2);
expect(rotated.filter(function(value){ return value === fileName1;}).length).toEqual(1);
expect(rotated.filter(function(value){ return value === fileName2;}).length).toEqual(1);
Expand All @@ -126,10 +165,10 @@ describe('File encryption tests', () => {
}, 5000);

it("should rotate key of all old encrypted files to unencrypted files", async function(done) {
const oldFileKey = 'oldKeyThatILoved';
const oldEncryptionKey = 'oldKeyThatILoved';
const oldEncryptedAdapter = new FileSystemAdapter({
filesSubDirectory: directory,
fileKey: oldFileKey
encryptionKey: oldEncryptionKey
});
const unEncryptedAdapter = new FileSystemAdapter({
filesSubDirectory: directory
Expand All @@ -152,7 +191,7 @@ describe('File encryption tests', () => {
expect(result.toString('utf-8')).toEqual(data2);
const oldEncryptedData2 = fs.readFileSync(filePath2);
//Check if unEncrypted adapter can read data and make sure it's not the same as oldEncrypted adapter
const {rotated, notRotated} = await unEncryptedAdapter.rotateFileKey({oldKey: oldFileKey});
const {rotated, notRotated} = await unEncryptedAdapter.rotateEncryptionKey({oldKey: oldEncryptionKey});
expect(rotated.length).toEqual(2);
expect(rotated.filter(function(value){ return value === fileName1;}).length).toEqual(1);
expect(rotated.filter(function(value){ return value === fileName2;}).length).toEqual(1);
Expand All @@ -171,14 +210,14 @@ describe('File encryption tests', () => {
}, 5000);

it("should only encrypt specified fileNames with the new key", async function(done) {
const oldFileKey = 'oldKeyThatILoved';
const oldEncryptionKey = 'oldKeyThatILoved';
const oldEncryptedAdapter = new FileSystemAdapter({
filesSubDirectory: directory,
fileKey: oldFileKey
encryptionKey: oldEncryptionKey
});
const encryptedAdapter = new FileSystemAdapter({
filesSubDirectory: directory,
fileKey: 'newKeyThatILove'
encryptionKey: 'newKeyThatILove'
});
const unEncryptedAdapter = new FileSystemAdapter({
filesSubDirectory: directory
Expand All @@ -205,7 +244,7 @@ describe('File encryption tests', () => {
const data3 = "hello past world";
await unEncryptedAdapter.createFile(fileName3, data3, 'text/utf8');
//Check if encrypted adapter can read data and make sure it's not the same as unEncrypted adapter
const {rotated, notRotated} = await encryptedAdapter.rotateFileKey({oldKey: oldFileKey, fileNames: [fileName1,fileName2]});
const {rotated, notRotated} = await encryptedAdapter.rotateEncryptionKey({oldKey: oldEncryptionKey, fileNames: [fileName1,fileName2]});
expect(rotated.length).toEqual(2);
expect(rotated.filter(function(value){ return value === fileName1;}).length).toEqual(1);
expect(rotated.filter(function(value){ return value === fileName2;}).length).toEqual(1);
Expand All @@ -225,14 +264,14 @@ describe('File encryption tests', () => {
}, 5000);

it("should return fileNames of those it can't encrypt with the new key", async function(done) {
const oldFileKey = 'oldKeyThatILoved';
const oldEncryptionKey = 'oldKeyThatILoved';
const oldEncryptedAdapter = new FileSystemAdapter({
filesSubDirectory: directory,
fileKey: oldFileKey
encryptionKey: oldEncryptionKey
});
const encryptedAdapter = new FileSystemAdapter({
filesSubDirectory: directory,
fileKey: 'newKeyThatILove'
encryptionKey: 'newKeyThatILove'
});
const unEncryptedAdapter = new FileSystemAdapter({
filesSubDirectory: directory
Expand Down Expand Up @@ -263,7 +302,7 @@ describe('File encryption tests', () => {
expect(result instanceof Buffer).toBe(true);
expect(result.toString('utf-8')).toEqual(data3);
//Check if encrypted adapter can read data and make sure it's not the same as unEncrypted adapter
const {rotated, notRotated} = await encryptedAdapter.rotateFileKey({oldKey: oldFileKey});
const {rotated, notRotated} = await encryptedAdapter.rotateEncryptionKey({oldKey: oldEncryptionKey});
expect(rotated.length).toEqual(2);
expect(rotated.filter(function(value){ return value === fileName1;}).length).toEqual(1);
expect(rotated.filter(function(value){ return value === fileName2;}).length).toEqual(1);
Expand Down
6 changes: 6 additions & 0 deletions spec/test.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@ describe('FileSystemAdapter tests', () => {

filesAdapterTests.testAdapter("FileSystemAdapter", fsAdapter);
})

describe('FileSystemAdapter tests - no options', () => {
var fsAdapter = new FileSystemAdapter();

filesAdapterTests.testAdapter("FileSystemAdapter", fsAdapter);
})