Enforce crypto settings is asserted correctly
MODIFY
src/server/rest/v1/service/SettingService.ts
src/server/rest/v1/service/SettingService.ts
@@ -7,21 +7,20 @@ import AppError from '../../../../exception/AppError';
import Authorizations from '../../../../authorization/Authorizations';
import Constants from '../../../../utils/Constants';
import Cypher from '../../../../utils/Cypher';
-import { LockEntity } from '../../../../types/Locking';
-import LockingManager from '../../../../locking/LockingManager';
import Logging from '../../../../utils/Logging';
import { ServerAction } from '../../../../types/Server';
import SettingSecurity from './security/SettingSecurity';
import SettingStorage from '../../../../storage/mongodb/SettingStorage';
import { StatusCodes } from 'http-status-codes';
import { TechnicalSettings } from '../../../../types/Setting';
+import Utils from '../../../../utils/Utils';
import UtilsService from './UtilsService';
import _ from 'lodash';
const MODULE_NAME = 'SettingService';
export default class SettingService {
- public static async handleDeleteSetting(action: ServerAction, req: Request, res: Response, next: NextFunction) {
+ public static async handleDeleteSetting(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Filter
const settingID = SettingSecurity.filterSettingRequestByID(req.query);
UtilsService.assertIdIsProvided(action, settingID, MODULE_NAME, 'handleDeleteSetting', req.user);
@@ -42,7 +41,7 @@ export default class SettingService {
// Delete
await SettingStorage.deleteSetting(req.user.tenantID, settingID);
// Log
- Logging.logSecurityInfo({
+ await Logging.logSecurityInfo({
tenantID: req.user.tenantID,
user: req.user, module: MODULE_NAME, method: 'handleDeleteSetting',
message: `Setting '${setting.identifier}' has been deleted successfully`,
@@ -54,7 +53,7 @@ export default class SettingService {
next();
}
- public static async handleGetSetting(action: ServerAction, req: Request, res: Response, next: NextFunction) {
+ public static async handleGetSetting(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Filter
const settingID = SettingSecurity.filterSettingRequestByID(req.query);
UtilsService.assertIdIsProvided(action, settingID, MODULE_NAME, 'handleGetSetting', req.user);
@@ -84,7 +83,7 @@ export default class SettingService {
next();
}
- public static async handleGetSettingByIdentifier(action: ServerAction, req: Request, res: Response, next: NextFunction) {
+ public static async handleGetSettingByIdentifier(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Filter
const settingID = SettingSecurity.filterSettingRequestByID(req.query);
UtilsService.assertIdIsProvided(action, settingID, MODULE_NAME, 'handleGetSettingByIdentifier', req.user);
@@ -114,7 +113,7 @@ export default class SettingService {
next();
}
- public static async handleGetSettings(action: ServerAction, req: Request, res: Response, next: NextFunction) {
+ public static async handleGetSettings(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check auth
if (!Authorizations.canListSettings(req.user)) {
throw new AppAuthError({
@@ -144,7 +143,7 @@ export default class SettingService {
next();
}
- public static async handleCreateSetting(action: ServerAction, req: Request, res: Response, next: NextFunction) {
+ public static async handleCreateSetting(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check auth
if (!Authorizations.canCreateSetting(req.user)) {
throw new AppAuthError({
@@ -164,7 +163,7 @@ export default class SettingService {
// Save Setting
filteredRequest.id = await SettingStorage.saveSettings(req.user.tenantID, filteredRequest);
// Log
- Logging.logSecurityInfo({
+ await Logging.logSecurityInfo({
tenantID: req.user.tenantID,
user: req.user, module: MODULE_NAME, method: 'handleCreateSetting',
message: `Setting '${filteredRequest.identifier}' has been created successfully`,
@@ -236,14 +235,47 @@ export default class SettingService {
setting.lastChangedBy = { 'id': req.user.id };
setting.lastChangedOn = new Date();
if (settingUpdate.identifier === TechnicalSettings.CRYPTO) {
+ // Check supported alogrithm
+ if (!Constants.CRYPTO_SUPPORTED_ALGORITHM.includes(
+ Utils.buildAlgorithm(settingUpdate.content.crypto.keyProperties))) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.CRYPTO_ALGORITHM_NOT_SUPPORTED,
+ message: 'Crypto algorithm not supported',
+ module: MODULE_NAME, method: 'handleUpdateSetting',
+ user: req.user
+ });
+ }
+ // Check crypto key
+ const keyLength = settingUpdate.content.crypto.keyProperties.blockSize / 8;
+ if (settingUpdate.content.crypto.key.length !== keyLength) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.CRYPTO_KEY_LENGTH_INVALID,
+ message: 'Crypto key length is invalid',
+ module: MODULE_NAME, method: 'handleUpdateSetting',
+ user: req.user
+ });
+ }
+ // Check if config is valid
+ try {
+ await Cypher.checkCryptoSettings(settingUpdate.content.crypto);
+ } catch (error) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.CRYPTO_CHECK_FAILED,
+ message: 'Crypto check failed to run: ' + error.message,
+ module: MODULE_NAME, method: 'handleUpdateSetting',
+ user: req.user
+ });
+ }
+ // Check if migration is on-going
if (setting.content.crypto.migrationToBeDone) {
- // If migration in progress, throw error
throw new AppError({
source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.MIGRATION_IN_PROGRESS,
- message: 'Sensitive Data Migration is in progress',
- module: MODULE_NAME,
- method: 'handleUpdateSetting',
+ errorCode: HTTPError.CRYPTO_MIGRATION_IN_PROGRESS,
+ message: 'Crypto migration is in progress',
+ module: MODULE_NAME, method: 'handleUpdateSetting',
user: req.user
});
} else {
@@ -263,7 +295,7 @@ export default class SettingService {
}
}
// Log
- Logging.logSecurityInfo({
+ await Logging.logSecurityInfo({
tenantID: req.user.tenantID,
user: req.user, module: MODULE_NAME, method: 'handleUpdateSetting',
message: `Setting '${settingUpdate.id}' has been updated successfully`,

MODIFY
src/types/HTTPError.ts
src/types/HTTPError.ts
@@ -58,8 +58,12 @@ export enum HTTPError {
THREE_PHASE_CHARGER_ON_SINGLE_PHASE_SITE_AREA = 539,
+ CRYPTO_MIGRATION_IN_PROGRESS = 511,
+ CRYPTO_ALGORITHM_NOT_SUPPORTED = 512,
+ CRYPTO_KEY_LENGTH_INVALID = 513,
+ CRYPTO_CHECK_FAILED = 514,
+
CANNOT_ACQUIRE_LOCK = 510,
- MIGRATION_IN_PROGRESS = 511,
INVALID_FILE_FORMAT = 583,
}

MODIFY
src/utils/Constants.ts
src/utils/Constants.ts
@@ -38,6 +38,117 @@ export default class Constants {
name: Constants.DEFAULT_TENANT
} as Tenant);
+ public static readonly CRYPTO_SUPPORTED_ALGORITHM = Object.freeze([
+ 'CAST-cbc',
+ 'aes-128-cbc',
+ 'aes-128-cbc-hmac-sha1',
+ 'aes-128-cfb',
+ 'aes-128-cfb1',
+ 'aes-128-cfb8',
+ 'aes-128-ctr',
+ 'aes-128-ecb',
+ 'aes-128-gcm',
+ 'aes-128-ofb',
+ 'aes-128-xts',
+ 'aes-192-cbc',
+ 'aes-192-cfb',
+ 'aes-192-cfb1',
+ 'aes-192-cfb8',
+ 'aes-192-ctr',
+ 'aes-192-ecb',
+ 'aes-192-gcm',
+ 'aes-192-ofb',
+ 'aes-256-cbc',
+ 'aes-256-cbc-hmac-sha1',
+ 'aes-256-cfb',
+ 'aes-256-cfb1',
+ 'aes-256-cfb8',
+ 'aes-256-ctr',
+ 'aes-256-ecb',
+ 'aes-256-gcm',
+ 'aes-256-ofb',
+ 'aes-256-xts',
+ 'aes128',
+ 'aes192',
+ 'aes256',
+ 'bf',
+ 'bf-cbc',
+ 'bf-cfb',
+ 'bf-ecb',
+ 'bf-ofb',
+ 'blowfish',
+ 'camellia-128-cbc',
+ 'camellia-128-cfb',
+ 'camellia-128-cfb1',
+ 'camellia-128-cfb8',
+ 'camellia-128-ecb',
+ 'camellia-128-ofb',
+ 'camellia-192-cbc',
+ 'camellia-192-cfb',
+ 'camellia-192-cfb1',
+ 'camellia-192-cfb8',
+ 'camellia-192-ecb',
+ 'camellia-192-ofb',
+ 'camellia-256-cbc',
+ 'camellia-256-cfb',
+ 'camellia-256-cfb1',
+ 'camellia-256-cfb8',
+ 'camellia-256-ecb',
+ 'camellia-256-ofb',
+ 'camellia128',
+ 'camellia192',
+ 'camellia256',
+ 'cast',
+ 'cast-cbc',
+ 'cast5-cbc',
+ 'cast5-cfb',
+ 'cast5-ecb',
+ 'cast5-ofb',
+ 'des',
+ 'des-cbc',
+ 'des-cfb',
+ 'des-cfb1',
+ 'des-cfb8',
+ 'des-ecb',
+ 'des-ede',
+ 'des-ede-cbc',
+ 'des-ede-cfb',
+ 'des-ede-ofb',
+ 'des-ede3',
+ 'des-ede3-cbc',
+ 'des-ede3-cfb',
+ 'des-ede3-cfb1',
+ 'des-ede3-cfb8',
+ 'des-ede3-ofb',
+ 'des-ofb',
+ 'des3',
+ 'desx',
+ 'desx-cbc',
+ 'id-aes128-GCM',
+ 'id-aes192-GCM',
+ 'id-aes256-GCM',
+ 'idea',
+ 'idea-cbc',
+ 'idea-cfb',
+ 'idea-ecb',
+ 'idea-ofb',
+ 'rc2',
+ 'rc2-40-cbc',
+ 'rc2-64-cbc',
+ 'rc2-cbc',
+ 'rc2-cfb',
+ 'rc2-ecb',
+ 'rc2-ofb',
+ 'rc4',
+ 'rc4-40',
+ 'rc4-hmac-md5',
+ 'seed',
+ 'seed-cbc',
+ 'seed-cfb',
+ 'seed-ecb',
+ 'seed-ofb'
+ ]);
+
public static readonly UNKNOWN_OBJECT_ID: string = '000000000000000000000000';
public static readonly UNKNOWN_STRING_ID: string = '000000000000000000000000';
public static readonly UNKNOWN_NUMBER_ID: number = -1;

MODIFY
src/utils/Cypher.ts
src/utils/Cypher.ts
@@ -46,6 +46,22 @@ export default class Cypher {
return crypto.createHash('sha256').update(data).digest('hex');
}
+ public static async checkCryptoSettings(cryptoSetting: CryptoSetting): Promise<void> {
+ const dataToEncrypt = 'test-data-to-encrypt';
+ // Cypher
+ const encryptedData = await Cypher.encrypt(null, dataToEncrypt, false, cryptoSetting);
+ // Decypher
+ const decryptedData = await Cypher.decrypt(null, encryptedData, false, cryptoSetting);
+ // Check
+ if (decryptedData !== dataToEncrypt) {
+ throw new BackendError({
+ source: Constants.CENTRAL_SERVER,
+ message: 'Crypto algorithm check failed',
+ module: MODULE_NAME, method: 'checkCryptoSettings',
+ });
+ }
+ }
+
public static async encryptSensitiveDataInJSON(tenantID: string, data: Record<string, any>, useFormerKey = false, cryptoSetting?: CryptoSetting): Promise<void> {
if (typeof data !== 'object') {
throw new BackendError({

Enforce crypto settings is asserted correctly