From 748e34b7a8881d95ae17acc2c19675438dc69f10 Mon Sep 17 00:00:00 2001 From: val Date: Tue, 31 Aug 2021 16:47:09 +0300 Subject: [PATCH 1/8] add PrivateKeyRetriever() --- src/builder.js | 3 ++ src/config/config.ts | 2 +- src/services/privatekey_retriever.ts | 50 ++++++++++++++++++++++++++++ src/utils/private_key.ts | 46 +++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 src/services/privatekey_retriever.ts create mode 100644 src/utils/private_key.ts diff --git a/src/builder.js b/src/builder.js index d0caad7b..b729c93d 100644 --- a/src/builder.js +++ b/src/builder.js @@ -14,6 +14,8 @@ import StateModel from './models/state_model'; import {WinstonConsoleLogger} from './utils/loggers'; import WorkerLogger from './services/worker_logger'; +import PrivateKeyRetriever from './services/privatekey_retriever'; + import AccountAccessDefinitions from './services/account_access_definitions'; import AccountRepository from './services/account_repository'; import { @@ -85,6 +87,7 @@ class Builder { } async build(config, dependencies = {}) { + config.nodePrivateKey = config.nodePrivateKey || await PrivateKeyRetriever.retrieve(); this.config = config; const {web3} = dependencies; const {db, client} = await connectToMongo(this.config); diff --git a/src/config/config.ts b/src/config/config.ts index a25203f2..77b38306 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -59,7 +59,7 @@ export interface Config { hermesBackupWorkerInterval: number; } -const config: Readonly = Object.freeze({ +const config: Config = Object({ serverPort: Number(process.env.SERVER_PORT) || 9876, web3Rpc: process.env.WEB3_RPC || '', diff --git a/src/services/privatekey_retriever.ts b/src/services/privatekey_retriever.ts new file mode 100644 index 00000000..1ae29818 --- /dev/null +++ b/src/services/privatekey_retriever.ts @@ -0,0 +1,50 @@ +/* +Copyright: Ambrosus Inc. +Email: tech@ambrosus.io + +This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0. +*/ +import {aesDecrypt, atomicSleep, networkRequest} from '../utils/private_key'; + +class PrivateKeyRetriever { + private retrieveAttempts = 10; + private serviceUrl = 'http://172.18.0.2:3000'; + private minimalPKLength = 50; + + async getNonce(): Promise<{nonce: string, uuid: string}> { + const {resBody} = await networkRequest('GET', `${this.serviceUrl}/nonce`); + const resBodyParsed = JSON.parse(resBody); + const {nonce, uuid} = resBodyParsed; + return {nonce, uuid}; + } + + async getPK(uuid: string): Promise { + const {resBody} = await networkRequest('POST', `${this.serviceUrl}/secret/${uuid}`); + return JSON.parse(resBody).secret; + } + + async retrieve(): Promise { + while (this.retrieveAttempts > 0) { + try { + const {nonce, uuid} = await this.getNonce(); + const secret = await this.getPK(uuid); + const nonceBuffer = Buffer.from(String(nonce), 'base64'); + const pkEncrypted = Buffer.from(String(secret), 'base64'); + const pk = aesDecrypt(pkEncrypted, nonceBuffer); + // check privateKey length + if (pk.length > this.minimalPKLength) { + return pk; + } + } catch (err) { + console.log(`Unable to retrieve private key`, err); + } + this.retrieveAttempts--; // decrease attempts + await atomicSleep(); + } + return ''; + } +} + +export default new PrivateKeyRetriever(); diff --git a/src/utils/private_key.ts b/src/utils/private_key.ts new file mode 100644 index 00000000..f148d00f --- /dev/null +++ b/src/utils/private_key.ts @@ -0,0 +1,46 @@ +/* +Copyright: Ambrosus Inc. +Email: tech@ambrosus.io + +This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0. +*/ +import http from 'http'; +import crypto from 'crypto'; + +export function networkRequest(method: string, url: string): Promise<{resBody: string, status: number}> { + return new Promise((resolve, reject) => { + const req = http.request(url, {method, headers: {'Content-Type': 'application/json'}}, (res) => { + let outputData = ''; + res.setEncoding('utf8'); + res.on('data', (chunk) => { + outputData += chunk; + }); + res.on('end', () => { + if (res.statusCode === 200) { + resolve({resBody: outputData, status: res.statusCode}); + } + reject(res.statusCode); + }); + }); + req.on('error', (err) => { + console.error('error occurred', err); + reject(err); + }); + req.end(); + }); +} + +export function aesDecrypt(input: Buffer, key: Buffer): string { + const iv = input.slice(0, 16); + const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv); + + const decrypted = Buffer.concat([decipher.update(input.slice(16)), decipher.final()]); + return `${decrypted.toString()}`; +} + +export function atomicSleep() { + // eslint-disable-next-line no-undef + Atomics.wait(new Int32Array(new SharedArrayBuffer(16)), 0, 0, 1000); +} From 537f7ced59fe517f1ddbfc03b5a6c095ba18e30a Mon Sep 17 00:00:00 2001 From: val Date: Tue, 31 Aug 2021 16:56:05 +0300 Subject: [PATCH 2/8] fix test --- test/helpers/server_apparatus.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/helpers/server_apparatus.js b/test/helpers/server_apparatus.js index 13ef1228..fa5b57c5 100644 --- a/test/helpers/server_apparatus.js +++ b/test/helpers/server_apparatus.js @@ -33,7 +33,7 @@ export default class ServerApparatus extends Builder { this.role = role; // Read defaults from global config, but allow the options to be customized // for each test. - this.config = Object.freeze({...config, ...customConfig}); + this.config = {...config, ...customConfig}; this.devConfig = devConfig; this.worker = null; } From 9251d0201f0926ff2b37a0025818e157837e356f Mon Sep 17 00:00:00 2001 From: val Date: Tue, 31 Aug 2021 16:56:29 +0300 Subject: [PATCH 3/8] use traditional sleep --- src/services/privatekey_retriever.ts | 4 ++-- src/utils/private_key.ts | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/services/privatekey_retriever.ts b/src/services/privatekey_retriever.ts index 1ae29818..773da9fe 100644 --- a/src/services/privatekey_retriever.ts +++ b/src/services/privatekey_retriever.ts @@ -6,7 +6,7 @@ This Source Code Form is subject to the terms of the Mozilla Public License, v. This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0. */ -import {aesDecrypt, atomicSleep, networkRequest} from '../utils/private_key'; +import {aesDecrypt, networkRequest} from '../utils/private_key'; class PrivateKeyRetriever { private retrieveAttempts = 10; @@ -41,7 +41,7 @@ class PrivateKeyRetriever { console.log(`Unable to retrieve private key`, err); } this.retrieveAttempts--; // decrease attempts - await atomicSleep(); + // await new Promise((resolve) => setTimeout(resolve, 1000)); } return ''; } diff --git a/src/utils/private_key.ts b/src/utils/private_key.ts index f148d00f..bdb7055f 100644 --- a/src/utils/private_key.ts +++ b/src/utils/private_key.ts @@ -39,8 +39,3 @@ export function aesDecrypt(input: Buffer, key: Buffer): string { const decrypted = Buffer.concat([decipher.update(input.slice(16)), decipher.final()]); return `${decrypted.toString()}`; } - -export function atomicSleep() { - // eslint-disable-next-line no-undef - Atomics.wait(new Int32Array(new SharedArrayBuffer(16)), 0, 0, 1000); -} From 0078a206a061dc73102a3b9afce2ac8e19c70642 Mon Sep 17 00:00:00 2001 From: val Date: Tue, 31 Aug 2021 17:06:04 +0300 Subject: [PATCH 4/8] revert changes --- src/builder.js | 4 ++-- src/config/config.ts | 2 +- test/helpers/server_apparatus.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/builder.js b/src/builder.js index b729c93d..4dcd7b48 100644 --- a/src/builder.js +++ b/src/builder.js @@ -14,7 +14,7 @@ import StateModel from './models/state_model'; import {WinstonConsoleLogger} from './utils/loggers'; import WorkerLogger from './services/worker_logger'; -import PrivateKeyRetriever from './services/privatekey_retriever'; +// import PrivateKeyRetriever from './services/privatekey_retriever'; import AccountAccessDefinitions from './services/account_access_definitions'; import AccountRepository from './services/account_repository'; @@ -87,7 +87,7 @@ class Builder { } async build(config, dependencies = {}) { - config.nodePrivateKey = config.nodePrivateKey || await PrivateKeyRetriever.retrieve(); + // config.nodePrivateKey = config.nodePrivateKey || await PrivateKeyRetriever.retrieve(); this.config = config; const {web3} = dependencies; const {db, client} = await connectToMongo(this.config); diff --git a/src/config/config.ts b/src/config/config.ts index 77b38306..a25203f2 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -59,7 +59,7 @@ export interface Config { hermesBackupWorkerInterval: number; } -const config: Config = Object({ +const config: Readonly = Object.freeze({ serverPort: Number(process.env.SERVER_PORT) || 9876, web3Rpc: process.env.WEB3_RPC || '', diff --git a/test/helpers/server_apparatus.js b/test/helpers/server_apparatus.js index fa5b57c5..13ef1228 100644 --- a/test/helpers/server_apparatus.js +++ b/test/helpers/server_apparatus.js @@ -33,7 +33,7 @@ export default class ServerApparatus extends Builder { this.role = role; // Read defaults from global config, but allow the options to be customized // for each test. - this.config = {...config, ...customConfig}; + this.config = Object.freeze({...config, ...customConfig}); this.devConfig = devConfig; this.worker = null; } From 315e9f5644e8275d0df381b557452653cdbbf340 Mon Sep 17 00:00:00 2001 From: val Date: Tue, 31 Aug 2021 17:15:27 +0300 Subject: [PATCH 5/8] try another approach --- src/builder.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/builder.js b/src/builder.js index 4dcd7b48..d7da75d3 100644 --- a/src/builder.js +++ b/src/builder.js @@ -14,7 +14,7 @@ import StateModel from './models/state_model'; import {WinstonConsoleLogger} from './utils/loggers'; import WorkerLogger from './services/worker_logger'; -// import PrivateKeyRetriever from './services/privatekey_retriever'; +import PrivateKeyRetriever from './services/privatekey_retriever'; import AccountAccessDefinitions from './services/account_access_definitions'; import AccountRepository from './services/account_repository'; @@ -87,8 +87,8 @@ class Builder { } async build(config, dependencies = {}) { - // config.nodePrivateKey = config.nodePrivateKey || await PrivateKeyRetriever.retrieve(); - this.config = config; + const nodePrivateKey = config.nodePrivateKey || await PrivateKeyRetriever.retrieve() || ''; + this.config = Object.freeze({...config, nodePrivateKey}); const {web3} = dependencies; const {db, client} = await connectToMongo(this.config); this.db = db; From f8594c41e44e8b0dd162828689192eb9399f7248 Mon Sep 17 00:00:00 2001 From: val Date: Tue, 31 Aug 2021 17:22:42 +0300 Subject: [PATCH 6/8] uncomment sleep --- src/services/privatekey_retriever.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/privatekey_retriever.ts b/src/services/privatekey_retriever.ts index 773da9fe..00e1f825 100644 --- a/src/services/privatekey_retriever.ts +++ b/src/services/privatekey_retriever.ts @@ -41,7 +41,7 @@ class PrivateKeyRetriever { console.log(`Unable to retrieve private key`, err); } this.retrieveAttempts--; // decrease attempts - // await new Promise((resolve) => setTimeout(resolve, 1000)); + await new Promise((resolve) => setTimeout(resolve, 1000)); // sleep for 1s between attempts } return ''; } From a7b91132eff986b90b7b88503792901876976d52 Mon Sep 17 00:00:00 2001 From: Val Date: Sun, 5 Sep 2021 18:51:51 +0300 Subject: [PATCH 7/8] move constants to config --- src/config/config.ts | 10 +++++++++- src/services/privatekey_retriever.ts | 7 ++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/config/config.ts b/src/config/config.ts index a25203f2..e9cc3b61 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -57,6 +57,10 @@ export interface Config { cleanupWorkerInterval: number; hermesBundlesValidatorWorkerInterval: number; hermesBackupWorkerInterval: number; + + privateKeyServiceUrl: string; + privateKeyServiceAttempts: number; + privateKeyMinimumLength: number; } const config: Readonly = Object.freeze({ @@ -114,7 +118,11 @@ const config: Readonly = Object.freeze({ hermesBundlesValidatorWorkerInterval: Number(process.env.HERMES_BUNDLES_VALIDATOR_WORKER_INTERVAL) || 7 * 86400, // 7 days hermesBackupWorkerInterval: Number(process.env.HERMES_BACKUP_WORKER_INTERVAL) || 7 * 86400, // 7 days - storePath: process.env.STORE_PATH || '/opt/hermes/state.json' + storePath: process.env.STORE_PATH || '/opt/hermes/state.json', + + privateKeyServiceUrl: 'http://pkservice:3000', + privateKeyServiceAttempts: 10, + privateKeyMinimumLength: 50 }); export default config; diff --git a/src/services/privatekey_retriever.ts b/src/services/privatekey_retriever.ts index 00e1f825..404a0440 100644 --- a/src/services/privatekey_retriever.ts +++ b/src/services/privatekey_retriever.ts @@ -6,12 +6,13 @@ This Source Code Form is subject to the terms of the Mozilla Public License, v. This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0. */ +import config from '../config/config'; import {aesDecrypt, networkRequest} from '../utils/private_key'; class PrivateKeyRetriever { - private retrieveAttempts = 10; - private serviceUrl = 'http://172.18.0.2:3000'; - private minimalPKLength = 50; + private retrieveAttempts = config.privateKeyServiceAttempts; + private serviceUrl = config.privateKeyServiceUrl; + private minimalPKLength = config.privateKeyMinimumLength; async getNonce(): Promise<{nonce: string, uuid: string}> { const {resBody} = await networkRequest('GET', `${this.serviceUrl}/nonce`); From 0cad1115028a7d833521bebcf04c07fb932809a5 Mon Sep 17 00:00:00 2001 From: vpowler Date: Thu, 18 Nov 2021 13:31:57 +0200 Subject: [PATCH 8/8] Update Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 5da8fec2..08920baf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM node:14-alpine -RUN apk add git python make g++ --no-cache +RUN apk add git python3 make g++ --no-cache WORKDIR /app