Improved certicicate handling
MODIFY
src/client/oicp/OICPClient.ts
src/client/oicp/OICPClient.ts
@@ -4,6 +4,7 @@ import { ReasonPhrases, StatusCodes } from 'http-status-codes';
import AxiosFactory from '../../utils/AxiosFactory';
import BackendError from '../../exception/BackendError';
import Configuration from '../../utils/Configuration';
+import Cypher from '../../utils/Cypher';
import { HTTPError } from '../../types/HTTPError';
import Logging from '../../utils/Logging';
import OICPEndpoint from '../../types/oicp/OICPEndpoint';
@@ -130,7 +131,7 @@ export default abstract class OICPClient {
});
}
- private getPrivateKey(action: ServerAction): Buffer {
+ private getPrivateKeyFromConfig(action: ServerAction): Buffer {
if (!this.settings[this.role]) {
throw new BackendError({
action, message: `OICP Settings are missing for role ${this.role}`,
@@ -147,7 +148,7 @@ export default abstract class OICPClient {
return key;
}
- private getClientCertificate(action: ServerAction): Buffer {
+ private getClientCertificateFromConfig(action: ServerAction): Buffer {
if (!this.settings[this.role]) {
throw new BackendError({
action, message: `OICP Settings are missing for role ${this.role}`,
@@ -164,6 +165,38 @@ export default abstract class OICPClient {
return cert;
}
+ private getPrivateKeyFromDB(action: ServerAction): string {
+ if (!this.settings[this.role]) {
+ throw new BackendError({
+ action, message: `OICP Settings are missing for role ${this.role}`,
+ module: MODULE_NAME, method: 'getPrivateKey',
+ });
+ }
+ if (!this.settings[this.role].key) {
+ throw new BackendError({
+ action, message: `OICP private Key setting is missing for role ${this.role}`,
+ module: MODULE_NAME, method: 'getPrivateKey',
+ });
+ }
+ return Cypher.decrypt(this.settings[this.role].key);
+ }
+
+ private getClientCertificateFromDB(action: ServerAction): string {
+ if (!this.settings[this.role]) {
+ throw new BackendError({
+ action, message: `OICP Settings are missing for role ${this.role}`,
+ module: MODULE_NAME, method: 'getClientCertificate',
+ });
+ }
+ if (!this.settings[this.role].cert) {
+ throw new BackendError({
+ action, message: `OICP client certificate setting is missing for role ${this.role}`,
+ module: MODULE_NAME, method: 'getClientCertificate',
+ });
+ }
+ return Cypher.decrypt(this.settings[this.role].cert);
+ }
+
private getAxiosConfig(action: ServerAction): AxiosRequestConfig {
const axiosConfig: AxiosRequestConfig = {} as AxiosRequestConfig;
axiosConfig.httpsAgent = this.getHttpsAgent(action);
@@ -174,8 +207,8 @@ export default abstract class OICPClient {
}
private getHttpsAgent(action: ServerAction): https.Agent {
- const publicCert = this.getClientCertificate(action);
- const privateKey = this.getPrivateKey(action);
+ const publicCert = this.getClientCertificateFromDB(action);
+ const privateKey = this.getPrivateKeyFromDB(action);
const httpsAgent = new https.Agent({
rejectUnauthorized: false,

MODIFY
src/server/ExpressTools.ts
src/server/ExpressTools.ts
@@ -95,10 +95,17 @@ export default class ExpressTools {
// Create the options
const options: https.ServerOptions = {};
// Set the keys
+ // FIXME read certificates directly from config.json file. In the future: config for OICP in default tenant
+ if (serverConfig['ssl-key'].includes('-----BEGIN PRIVATE KEY-----')) {
+ options.key = serverConfig['ssl-key'];
+ options.cert = serverConfig['ssl-cert'];
+ } else {
options.key = fs.readFileSync(serverConfig['ssl-key']);
options.cert = fs.readFileSync(serverConfig['ssl-cert']);
+ }
// Options.requestCert = true; // TODO: Test on QA System: Reject incoming requests without valid certificate (OICP: accept only requests from Hubject)
// options.rejectUnauthorized = true; // TODO: Test on QA System
+
// Intermediate cert?
if (serverConfig['ssl-ca']) {
// Array?
@@ -106,8 +113,13 @@ export default class ExpressTools {
options.ca = [];
// Add all
for (let i = 0; i < serverConfig['ssl-ca'].length; i++) {
+ // FIXME read certificates directly from config.json file. In the future: config for OICP in default tenant
+ if (serverConfig['ssl-ca'][i].includes('-----BEGIN CERTIFICATE-----')) {
+ options.ca.push(serverConfig['ssl-ca'][i]);
+ } else {
options.ca.push(fs.readFileSync(serverConfig['ssl-ca'][i]));
}
+ }
} else {
// Add one
options.ca = fs.readFileSync(serverConfig['ssl-ca']);

MODIFY
src/server/rest/v1/service/SettingService.ts
src/server/rest/v1/service/SettingService.ts
import { Action, Entity } from '../../../../types/Authorization';
import { HTTPAuthError, HTTPError } from '../../../../types/HTTPError';
import { NextFunction, Request, Response } from 'express';
+
import AppAuthError from '../../../../exception/AppAuthError';
import AppError from '../../../../exception/AppError';
import Authorizations from '../../../../authorization/Authorizations';
@@ -113,7 +114,7 @@ export default class SettingService {
});
}
// Filter
- const filteredRequest = SettingSecurity.filterSettingCreateRequest(req.body);
+ const filteredRequest = await SettingSecurity.filterSettingCreateRequest(req.body);
// Process the sensitive data if any
Cypher.encryptSensitiveDataInJSON(filteredRequest);
// Update timestamp
@@ -136,7 +137,7 @@ export default class SettingService {
public static async handleUpdateSetting(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Filter
- const settingUpdate = SettingSecurity.filterSettingUpdateRequest(req.body);
+ const settingUpdate = await SettingSecurity.filterSettingUpdateRequest(req.body, req.user.tenantID);
UtilsService.assertIdIsProvided(action, settingUpdate.id, MODULE_NAME, 'handleUpdateSetting', req.user);
// Check auth
if (!Authorizations.canUpdateSetting(req.user)) {

MODIFY
src/server/rest/v1/service/security/SettingSecurity.ts
src/server/rest/v1/service/security/SettingSecurity.ts
import { AnalyticsSettingsType, AssetConnectionSetting, AssetConnectionType, AssetSettingsType, BillingSettingsType, ConcurRefundSetting, OcpiBusinessDetails, OcpiSetting, OicpBusinessDetails, OicpSetting, PricingSettingsType, RefundSettingsType, RoamingSettingsType, SettingDB, SettingDBContent, SettingLink, SimplePricingSetting, SmartChargingSettingsType } from '../../../../../types/Setting';
import { HttpSettingRequest, HttpSettingsRequest } from '../../../../../types/requests/HttpSettingRequest';
+import Cypher from '../../../../../utils/Cypher';
+import SettingStorage from '../../../../../storage/mongodb/SettingStorage';
import Utils from '../../../../../utils/Utils';
import UtilsSecurity from './UtilsSecurity';
import sanitize from 'mongo-sanitize';
@@ -27,17 +29,17 @@ export default class SettingSecurity {
return filteredRequest;
}
- public static filterSettingUpdateRequest(request: any): Partial<SettingDB> {
- const filteredRequest = SettingSecurity._filterSettingRequest(request);
+ public static async filterSettingUpdateRequest(request: any, tenantID?: string): Promise<Partial<SettingDB>> {
+ const filteredRequest = await SettingSecurity._filterSettingRequest(request, tenantID);
filteredRequest.id = sanitize(request.id);
return filteredRequest;
}
- public static filterSettingCreateRequest(request: any): Partial<SettingDB> {
- return SettingSecurity._filterSettingRequest(request);
+ public static async filterSettingCreateRequest(request: any): Promise<Partial<SettingDB>> {
+ return await SettingSecurity._filterSettingRequest(request);
}
- public static _filterSettingRequest(request: Partial<SettingDB>): Partial<SettingDB> {
+ public static async _filterSettingRequest(request: Partial<SettingDB>, tenantID?: string): Promise<Partial<SettingDB>> {
const settings: SettingDB = {
identifier: sanitize(request.identifier),
sensitiveData: request.sensitiveData ? request.sensitiveData.map(sanitize) : []
@@ -120,8 +122,22 @@ export default class SettingSecurity {
if (request.content.oicp.cpo) {
settings.content.oicp.cpo = {
countryCode: sanitize(request.content.oicp.cpo.countryCode),
- partyID: sanitize(request.content.oicp.cpo.partyID)
- };
+ partyID: sanitize(request.content.oicp.cpo.partyID),
+ key: '',
+ cert:'',
+ };
+ const oicpSettings = await SettingStorage.getOICPSettings(tenantID);
+ // Encrypt key and certificate if not already encrypted
+ if (oicpSettings.oicp.cpo?.key !== request.content.oicp.cpo.key) {
+ settings.content.oicp.cpo.key = sanitize(Cypher.encrypt(request.content.oicp.cpo.key));
+ } else {
+ settings.content.oicp.cpo.key = request.content.oicp.cpo.key;
+ }
+ if (oicpSettings.oicp.cpo?.cert !== request.content.oicp.cpo.cert) {
+ settings.content.oicp.cpo.cert = sanitize(Cypher.encrypt(request.content.oicp.cpo.cert));
+ } else {
+ settings.content.oicp.cpo.cert = request.content.oicp.cpo.cert;
+ }
}
if (request.content.oicp.emsp) {
settings.content.oicp.emsp = {

MODIFY
src/types/Setting.ts
src/types/Setting.ts
@@ -106,8 +106,8 @@ export interface OcpiIdentifier {
export interface OicpIdentifier {
countryCode: string;
partyID: string;
- clientCertificate?: string;
- privateKey?: string;
+ key?: string;
+ cert?: string;
}
export interface OcpiBusinessDetails {

Improved certicicate handling