Skip to content

Commit

Permalink
use cryptr library for encryption/decryption.
Browse files Browse the repository at this point in the history
remove db encryption configuration.
update tests

Signed-off-by: Jason Sherman <[email protected]>
  • Loading branch information
usingtechnology committed Jun 21, 2024
1 parent 1749b91 commit 00f115f
Show file tree
Hide file tree
Showing 10 changed files with 46 additions and 130 deletions.
3 changes: 3 additions & 0 deletions .devcontainer/chefs_local/local.json.sample
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
"windowMs": "900000",
"max": "100"
}
},
"encryption": {
"proxy": "352f7c24819086bf3df5a38c1a40586045f73e0007440c9d27d59ee8560e3fe7"
}
},
"serviceClient": {
Expand Down
3 changes: 1 addition & 2 deletions app/config/custom-environment-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@
"logLevel": "SERVER_LOGLEVEL",
"port": "SERVER_PORT",
"encryption": {
"proxy": "SERVER_ENCRYPTION_PROXY",
"db": "SERVER_ENCRYPTION_DB"
"proxy": "SERVER_ENCRYPTION_PROXY"
}
},
"serviceClient": {
Expand Down
3 changes: 1 addition & 2 deletions app/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@
}
},
"encryption": {
"proxy": "352f7c24819086bf3df5a38c1a40586045f73e0007440c9d27d59ee8560e3fe7",
"db": "728160b156ad4fd97f8fe6c5c2d23d8b543acc9d04e5002ae652a10285ff9fe4"
"proxy": "352f7c24819086bf3df5a38c1a40586045f73e0007440c9d27d59ee8560e3fe7"
}
},
"serviceClient": {
Expand Down
39 changes: 25 additions & 14 deletions app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"compression": "^1.7.4",
"config": "^3.3.9",
"cors": "^2.8.5",
"cryptr": "^6.3.0",
"express": "^4.19.2",
"express-basic-auth": "^1.2.1",
"express-rate-limit": "^7.2.0",
Expand Down
61 changes: 7 additions & 54 deletions app/src/components/encryptionService.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const config = require('config');
const crypto = require('crypto');
const Cryptr = require('cryptr');

const SERVICE = 'EncryptionService';

Expand Down Expand Up @@ -30,52 +30,14 @@ class Aes256Gcm extends Encryption {
// crypto.createHash('sha256').update("sometext").digest('hex');
//
encrypt(payload, masterkey) {
// random initialization vector
const iv = crypto.randomBytes(16);

// random salt
const salt = crypto.randomBytes(64);

// derive encryption key: 32 byte key length
// in assumption the masterkey is a cryptographic and NOT a password there is no need for
// a large number of iterations. It may can replaced by HKDF
// the value of 2145 is randomly chosen!
const key = crypto.pbkdf2Sync(masterkey, salt, 2145, 32, 'sha512');

// AES 256 GCM Mode
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);

const cryptr = new Cryptr(masterkey);
// encrypt the given text/json
const strPayload = typeof payload === 'string' || payload instanceof String ? payload : JSON.stringify(payload);
const encrypted = Buffer.concat([cipher.update(strPayload, 'utf8'), cipher.final()]);

// extract the auth tag
const tag = cipher.getAuthTag();

// generate output
return Buffer.concat([salt, iv, tag, encrypted]).toString('base64');
const strPayload = typeof payload === 'string' || payload instanceof String ? payload : JSON.stringify(payload); // random initialization vector
return cryptr.encrypt(strPayload);
}
decrypt(encdata, masterkey) {
// base64 decoding
const bData = Buffer.from(encdata, 'base64');

// convert data to buffers
const salt = bData.subarray(0, 64);
const iv = bData.subarray(64, 80);
const tag = bData.subarray(80, 96);
const payload = bData.subarray(96);

// derive key using; 32 byte key length
const key = crypto.pbkdf2Sync(masterkey, salt, 2145, 32, 'sha512');

// AES 256 GCM Mode
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);

// encrypt the given text
const decrypted = decipher.update(payload, 'binary', 'utf8') + decipher.final('utf8');

return decrypted;
const cryptr = new Cryptr(masterkey);
return cryptr.decrypt(encdata);
}
}

Expand Down Expand Up @@ -138,20 +100,11 @@ class EncryptionService {
decryptProxy(payload) {
return this.decrypt(ENCRYPTION_ALGORITHMS.AES_256_GCM, ENCRYPTION_KEYS.PROXY, payload);
}

encryptDb(payload) {
return this.encrypt(ENCRYPTION_ALGORITHMS.AES_256_GCM, ENCRYPTION_KEYS.DATABASE, payload);
}

decryptDb(payload) {
return this.decrypt(ENCRYPTION_ALGORITHMS.AES_256_GCM, ENCRYPTION_KEYS.DATABASE, payload);
}
}

const proxy = config.get('server.encryption.proxy');
const db = config.get('server.encryption.db');

const keys = { proxy: proxy, db: db };
const keys = { proxy: proxy };
const algorithms = { 'aes-256-gcm': new Aes256Gcm() };

let encryptionService = new EncryptionService({
Expand Down
32 changes: 0 additions & 32 deletions app/src/forms/common/models/tables/externalAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ const { Timestamps } = require('../mixins');
const { Regex } = require('../../constants');
const stamps = require('../jsonSchema').stamps;

const { encryptionService } = require('../../../../components/encryptionService');

class ExternalAPI extends Timestamps(Model) {
static get tableName() {
return 'external_api';
Expand Down Expand Up @@ -45,36 +43,6 @@ class ExternalAPI extends Timestamps(Model) {
};
}

async $beforeInsert(context) {
await super.$beforeInsert(context);
if (this.apiKey) {
this.apiKey = encryptionService.encryptDb(this.apiKey);
}
if (this.userInfoEncryptionKey) {
this.userInfoEncryptionKey = encryptionService.encryptDb(this.userInfoEncryptionKey);
}
}

async $beforeUpdate(context) {
await super.$beforeUpdate(context);
if (this.apiKey) {
this.apiKey = encryptionService.encryptDb(this.apiKey);
}
if (this.userInfoEncryptionKey) {
this.userInfoEncryptionKey = encryptionService.encryptDb(this.userInfoEncryptionKey);
}
}

async $afterFind(context) {
await super.$afterFind(context);
if (this.apiKey) {
this.apiKey = encryptionService.decryptDb(this.apiKey);
}
if (this.userInfoEncryptionKey) {
this.userInfoEncryptionKey = encryptionService.decryptDb(this.userInfoEncryptionKey);
}
}

static get jsonSchema() {
return {
type: 'object',
Expand Down
25 changes: 4 additions & 21 deletions app/tests/unit/components/encryptionService.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const { MockModel } = require('../../common/dbHelper');
const { encryptionService, ENCRYPTION_ALGORITHMS, ENCRYPTION_KEYS } = require('../../../src/components/encryptionService');

// change these as appropriate after adding new default keys/algos...
const KEY_COUNT = 2;
const KEY_COUNT = 1;
const ALGO_COUNT = 1;

beforeEach(() => {
Expand Down Expand Up @@ -42,30 +42,13 @@ describe('encryptionService', () => {
expect(data).toEqual(dec);
});

it('should encrypt/decrypt db data (object)', () => {
const data = { username: 'unittest', email: '[email protected]' };
const enc = encryptionService.encryptDb(data);
expect(enc).toBeTruthy();
const dec = encryptionService.decryptDb(enc);
expect(dec).toBeTruthy();
expect(data).toMatchObject(JSON.parse(dec));
});

it('should encrypt/decrypt db data (string)', () => {
const data = 'this is my string value';
const enc = encryptionService.encryptDb(data);
expect(enc).toBeTruthy();
const dec = encryptionService.decryptDb(enc);
expect(dec).toBeTruthy();
expect(data).toEqual(dec);
});

it('should not decrypt a proxy encryption with db key', () => {
it('should not decrypt a proxy encryption with external key', () => {
const externalKey = 'e9eb43121581f1877e2b8135c8d9079b91c04aab6c717799196630a685b2c6c0';
const data = 'this is my string value';
const enc = encryptionService.encryptProxy(data);
expect(enc).toBeTruthy();
expect(() => {
encryptionService.decryptDb(enc);
encryptionService.decryptExternal(ENCRYPTION_ALGORITHMS.AES_256_GCM, externalKey, enc);
}).toThrowError();
});

Expand Down
3 changes: 2 additions & 1 deletion app/tests/unit/forms/proxy/service.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ describe('Proxy Service', () => {
});

it('should throw error if payload uses non-proxy encryption', async () => {
const data = encryptionService.encryptDb(goodProxyHeaderInfo);
const externalKey = 'e9eb43121581f1877e2b8135c8d9079b91c04aab6c717799196630a685b2c6c0';
const data = encryptionService.encryptExternal(ENCRYPTION_ALGORITHMS.AES_256_GCM, externalKey, goodProxyHeaderInfo);
await expect(service.readProxyHeaders({ 'X-CHEFS-PROXY-DATA': data })).rejects.toThrow();
});

Expand Down
6 changes: 2 additions & 4 deletions openshift/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,19 +144,17 @@ oc create -n $NAMESPACE secret generic $APP_NAME-objectstorage-secret \
--from-literal=password=$password
```

We need to store encryption keys as secrets. These keys are used to handle communication between the frontend and external APIS (proxy) and storing key data in the database (db). In both cases we will be using `aes-256-gcm` for the encryption and keys for `aes-256-gcm` should be sha256 hashes: 256 bits/32 bytes/64 characters.
We need to store encryption keys as secrets. These keys are used to handle communication between the frontend and external APIS (proxy). We will be using `aes-256-gcm` for the encryption and keys for `aes-256-gcm` should be sha256 hashes: 256 bits/32 bytes/64 characters.

Using `node.js` you can generate keys: `crypto.createHash('sha256').update("some seed text").digest('hex');`

```sh

export proxy_key=<proxy generated hash value>
export db_key=<db generated hash value>

oc create -n $NAMESPACE secret generic $APP_NAME-encryption-keys \
--type=Opaque \
--from-literal=proxy=$proxy_key \
--from-literal=db=$db_key
--from-literal=proxy=$proxy_key
```

## Deployment
Expand Down

0 comments on commit 00f115f

Please sign in to comment.