cypher/decypher using crypto key from db
MODIFY
src/integration/asset/schneider/SchneiderAssetIntegration.ts
src/integration/asset/schneider/SchneiderAssetIntegration.ts
@@ -105,7 +105,7 @@ export default class SchneiderAssetIntegration extends AssetIntegration<AssetSet
// Check if connection is initialized
this.checkConnectionIsProvided();
// Get credential params
- const credentials = this.getCredentialURLParams();
+ const credentials = await this.getCredentialURLParams();
// Send credentials to get the token
const response = await Utils.executePromiseWithTimeout(5000,
this.axiosInstance.post(`${this.connection.url}/GetToken`,
@@ -123,11 +123,13 @@ export default class SchneiderAssetIntegration extends AssetIntegration<AssetSet
return response.data.access_token;
}
- private getCredentialURLParams(): URLSearchParams {
+ private async getCredentialURLParams(): Promise<URLSearchParams> {
const params = new URLSearchParams();
params.append('grant_type', 'password');
params.append('username', this.connection.connection.user);
- params.append('password', Cypher.decrypt(this.connection.connection.password));
+ // Get Crypto Setting
+ const cryptoSetting = await Cypher.getCryptoSetting(this.tenantID);
+ params.append('password', Cypher.decrypt(this.connection.connection.password, cryptoSetting));
return params;
}

MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
@@ -29,21 +29,18 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
private static readonly STRIPE_MAX_LIST = 100;
private axiosInstance: AxiosInstance;
private stripe: Stripe;
+ private stripeSettings: StripeBillingSetting;
+ // TODO refactor
constructor(tenantId: string, settings: StripeBillingSetting) {
super(tenantId, settings);
this.axiosInstance = AxiosFactory.getAxiosInstance(this.tenantID);
- this.settings.currency = settings.currency;
- if (this.settings.secretKey) {
- this.settings.secretKey = Cypher.decrypt(settings.secretKey);
- }
- // Currently the public key is not encrypted
- this.stripe = new Stripe(this.settings.secretKey);
+ this.stripeSettings = settings;
}
public async checkConnection(): Promise<void> {
// Check Stripe
- this.checkIfStripeIsInitialized();
+ await this.initializeStripe();
// Check Key
if (!this.settings.secretKey) {
throw new BackendError({
@@ -694,14 +691,14 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
};
}
- private checkIfStripeIsInitialized() {
- if (!this.stripe) {
- throw new BackendError({
- source: Constants.CENTRAL_SERVER,
- module: MODULE_NAME, method: 'checkIfStripeIsInitialized',
- action: ServerAction.CHECK_CONNECTION,
- message: 'No connection to Stripe available'
- });
+ private async initializeStripe() {
+ this.settings.currency = this.stripeSettings.currency;
+ // Get Crypto Setting
+ const cryptoSetting = await Cypher.getCryptoSetting(this.tenantID);
+ if (this.settings.secretKey) {
+ this.settings.secretKey = Cypher.decrypt(this.stripeSettings.secretKey, cryptoSetting);
}
+ // Currently the public key is not encrypted
+ this.stripe = new Stripe(this.settings.secretKey);
}
}

MODIFY
src/migration/tasks/AddSensitiveDataInSettingsTask.ts
src/migration/tasks/AddSensitiveDataInSettingsTask.ts
@@ -25,6 +25,8 @@ export default class AddSensitiveDataInSettingsTask extends MigrationTask {
}
}])
.toArray();
+ // Get Crypto Setting
+ const cryptoSetting = await Cypher.getCryptoSetting(tenant.id);
// Process each setting
for (const setting of settings) {
// Add sensitiveData property if not present
@@ -34,7 +36,7 @@ export default class AddSensitiveDataInSettingsTask extends MigrationTask {
setting.sensitiveData = ['content.concur.clientSecret'];
// Encrypt
if (setting.content.concur.clientSecret) {
- setting.content.concur.clientSecret = Cypher.encrypt(setting.content.concur.clientSecret);
+ setting.content.concur.clientSecret = Cypher.encrypt(setting.content.concur.clientSecret, cryptoSetting);
} else {
setting.content.concur.clientSecret = '';
}
@@ -42,7 +44,7 @@ export default class AddSensitiveDataInSettingsTask extends MigrationTask {
} else if (setting.content.type === PricingSettingsType.CONVERGENT_CHARGING) {
setting.sensitiveData = ['content.convergentCharging.password'];
if (setting.content.convergentCharging.password) {
- setting.content.convergentCharging.password = Cypher.encrypt(setting.content.convergentCharging.password);
+ setting.content.convergentCharging.password = Cypher.encrypt(setting.content.convergentCharging.password, cryptoSetting);
} else {
setting.content.convergentCharging.password = '';
}

MODIFY
src/server/rest/v1/service/SettingService.ts
src/server/rest/v1/service/SettingService.ts
@@ -115,8 +115,10 @@ export default class SettingService {
}
// Filter
const filteredRequest = SettingSecurity.filterSettingCreateRequest(req.body);
+ // Get Crypto Setting
+ const cryptoSetting = await Cypher.getCryptoSetting(req.user.tenantID);
// Process the sensitive data if any
- Cypher.encryptSensitiveDataInJSON(filteredRequest);
+ Cypher.encryptSensitiveDataInJSON(filteredRequest, cryptoSetting);
// Update timestamp
filteredRequest.createdBy = { 'id': req.user.id };
filteredRequest.createdOn = new Date();
@@ -166,6 +168,8 @@ export default class SettingService {
user: req.user
});
}
+ // Get Crypto Setting
+ const cryptoSetting = await Cypher.getCryptoSetting(req.user.tenantID);
// Process sensitive properties
for (const property of settingUpdate.sensitiveData) {
// Get the sensitive property from the request
@@ -177,14 +181,14 @@ export default class SettingService {
const hashedValueInDB = Cypher.hash(valueInDb);
if (valueInRequest !== hashedValueInDB) {
// Yes: Encrypt
- _.set(settingUpdate, property, Cypher.encrypt(valueInRequest));
+ _.set(settingUpdate, property, Cypher.encrypt(valueInRequest, cryptoSetting));
} else {
// No: Put back the encrypted value
_.set(settingUpdate, property, valueInDb);
}
} else {
// Value in db is empty then encrypt
- _.set(settingUpdate, property, Cypher.encrypt(valueInRequest));
+ _.set(settingUpdate, property, Cypher.encrypt(valueInRequest, cryptoSetting));
}
}
}

MODIFY
src/utils/Cypher.ts
src/utils/Cypher.ts
@@ -2,6 +2,9 @@ import BackendError from '../exception/BackendError';
import Configuration from './Configuration';
import Constants from './Constants';
import CryptoConfiguration from '../types/configuration/CryptoConfiguration';
+import { CryptoSetting } from '../types/Setting';
+import SettingStorage from '../storage/mongodb/SettingStorage';
+import Utils from './Utils';
import _ from 'lodash';
import crypto from 'crypto';
@@ -9,36 +12,24 @@ const IV_LENGTH = 16;
const MODULE_NAME = 'Cypher';
export default class Cypher {
- private static configuration: CryptoConfiguration;
- public static getConfiguration(): CryptoConfiguration {
- if (!this.configuration) {
- this.configuration = Configuration.getCryptoConfig();
- if (!this.configuration) {
- throw new BackendError({
- source: Constants.CENTRAL_SERVER,
- module: MODULE_NAME,
- method: 'getConfiguration',
- message: 'Crypto configuration is missing'
- });
- }
- }
- return this.configuration;
+ public static async getCryptoSetting(tenantID: string): Promise<CryptoSetting> {
+ return (await SettingStorage.getCryptoSettings(tenantID)).crypto;
}
- public static encrypt(data: string): string {
+ public static encrypt(data: string, cryptoSetting: CryptoSetting): string {
const iv = crypto.randomBytes(IV_LENGTH);
- const cipher = crypto.createCipheriv(Cypher.getConfiguration().algorithm, Buffer.from(Cypher.getConfiguration().key), iv);
+ const cipher = crypto.createCipheriv(Utils.buildAlgorithm(cryptoSetting.keyProperties), Buffer.from(cryptoSetting.key), iv);
let encryptedData = cipher.update(data);
encryptedData = Buffer.concat([encryptedData, cipher.final()]);
return iv.toString('hex') + ':' + encryptedData.toString('hex');
}
- public static decrypt(data: string): string {
+ public static decrypt(data: string, cryptoSetting: CryptoSetting): string {
const dataParts = data.split(':');
const iv = Buffer.from(dataParts.shift(), 'hex');
const encryptedData = Buffer.from(dataParts.join(':'), 'hex');
- const decipher = crypto.createDecipheriv(Cypher.getConfiguration().algorithm, Buffer.from(Cypher.getConfiguration().key), iv);
+ const decipher = crypto.createDecipheriv(Utils.buildAlgorithm(cryptoSetting.keyProperties), Buffer.from(cryptoSetting.key), iv);
let decrypted = decipher.update(encryptedData);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
@@ -48,7 +39,7 @@ export default class Cypher {
return crypto.createHash('sha256').update(data).digest('hex');
}
- public static encryptSensitiveDataInJSON(obj: Record<string, any>): void {
+ public static encryptSensitiveDataInJSON(obj: Record<string, any>, cryptoSetting: CryptoSetting): void {
if (typeof obj !== 'object') {
throw new BackendError({
source: Constants.CENTRAL_SERVER,
@@ -73,7 +64,7 @@ export default class Cypher {
const value = _.get(obj, property);
// If the value is undefined, null or empty then do nothing and skip to the next property
if (value && value.length > 0) {
- _.set(obj, property, Cypher.encrypt(value));
+ _.set(obj, property, Cypher.encrypt(value, cryptoSetting));
}
}
}
@@ -82,7 +73,7 @@ export default class Cypher {
}
}
- public static decryptSensitiveDataInJSON(obj: Record<string, any>): void {
+ public static decryptSensitiveDataInJSON(obj: Record<string, any>, cryptoSetting: CryptoSetting): void {
if (typeof obj !== 'object') {
throw new BackendError({
source: Constants.CENTRAL_SERVER,
@@ -107,7 +98,7 @@ export default class Cypher {
const value = _.get(obj, property);
// If the value is undefined, null or empty then do nothing and skip to the next property
if (value && value.length > 0) {
- _.set(obj, property, Cypher.decrypt(value));
+ _.set(obj, property, Cypher.decrypt(value, cryptoSetting));
}
}
}

MODIFY
src/utils/Utils.ts
src/utils/Utils.ts
@@ -1353,6 +1353,9 @@ export default class Utils {
keySize: parseInt(regExp[2]),
operationMode: regExp[3].toUpperCase()
};
+ }
+ public static buildAlgorithm(properties: CryptoKeyProperties): string {
+ return `${properties.blockCypher.toLowerCase()}-${properties.keySize}-${properties.operationMode.toLowerCase()}`;
}
}

MODIFY
test/api/BillingTest.ts
test/api/BillingTest.ts
@@ -45,14 +45,18 @@ class TestData {
public static async setBillingSystemValidCredentials(testData) {
const stripeSettings = TestData.getStripeSettings();
await TestData.saveBillingSettings(testData, stripeSettings);
- stripeSettings.secretKey = Cypher.encrypt(stripeSettings.secretKey);
+ // Get Crypto Setting
+ const cryptoSetting = await Cypher.getCryptoSetting(testData.tenantContext.getTenant().id);
+ stripeSettings.secretKey = Cypher.encrypt(stripeSettings.secretKey, cryptoSetting);
billingImpl = new StripeBillingIntegration(testData.tenantContext.getTenant().id, stripeSettings);
expect(billingImpl).to.not.be.null;
}
public static async setBillingSystemInvalidCredentials(testData) {
const stripeSettings = TestData.getStripeSettings();
- stripeSettings.secretKey = Cypher.encrypt('sk_test_invalid_credentials');
+ // Get Crypto Setting
+ const cryptoSetting = await Cypher.getCryptoSetting(testData.tenantContext.getTenant().id);
+ stripeSettings.secretKey = Cypher.encrypt('sk_test_invalid_credentials', cryptoSetting);
await TestData.saveBillingSettings(testData, stripeSettings);
billingImpl = new StripeBillingIntegration(testData.tenantContext.getTenant().id, stripeSettings);
expect(billingImpl).to.not.be.null;

MODIFY
test/api/CypherTest.ts
test/api/CypherTest.ts
@@ -18,6 +18,8 @@ const FAKE_JSON: CypherJSON = { 'sensitiveData': ['content.secret1', 'content.se
const FAKE_WORD = 'Wingardium Leviosa';
const FAKE_WORD_ENCRYPTED = '73d49673e8f70de6e16ba99bc0c273da:8b9cb41f960d2b50604921a68d7d0382472d';
+
+// TODO No point of testing this without tenant? How to change here?
describe('Cypher Tests', function() {
this.timeout(30000);

MODIFY
test/api/SmartChargingTest.ts
test/api/SmartChargingTest.ts
@@ -47,7 +47,9 @@ class TestData {
public static async setSmartChargingValidCredentials(testData): Promise<void> {
const sapSmartChargingSettings = TestData.getSmartChargingSettings();
await TestData.saveSmartChargingSettings(testData, sapSmartChargingSettings);
- sapSmartChargingSettings.password = Cypher.encrypt(sapSmartChargingSettings.password);
+ // Get Crypto Setting
+ const cryptoSetting = await Cypher.getCryptoSetting(testData.tenantContext.getTenant().id);
+ sapSmartChargingSettings.password = Cypher.encrypt(sapSmartChargingSettings.password, cryptoSetting);
aCBufferFactor = 1 + sapSmartChargingSettings.limitBufferAC / 100,
dCBufferFactor = 1 + sapSmartChargingSettings.limitBufferDC / 100,
smartChargingIntegration = await SmartChargingFactory.getSmartChargingImpl(testData.tenantContext.getTenant().id);

MODIFY
test/api/context/BillingContext.ts
test/api/context/BillingContext.ts
@@ -85,7 +85,9 @@ export default class BillingContext {
const tenantBillingSettings = await SettingStorage.getBillingSettings(this.tenantContext.getTenant().id);
tenantBillingSettings.stripe = stripeSettings;
tenantBillingSettings.sensitiveData = ['content.stripe.secretKey'];
- tenantBillingSettings.stripe.secretKey = Cypher.encrypt(stripeSettings.secretKey);
+ // Get Crypto Setting
+ const cryptoSetting = await Cypher.getCryptoSetting(this.tenantContext.getTenant().id);
+ tenantBillingSettings.stripe.secretKey = Cypher.encrypt(stripeSettings.secretKey, cryptoSetting);
await SettingStorage.saveBillingSettings(this.tenantContext.getTenant().id, tenantBillingSettings);
}
}

cypher/decypher using crypto key from db