From 4126fd503a0d4a7974b901a8eef8536a37cb7a01 Mon Sep 17 00:00:00 2001 From: emad-salah Date: Mon, 20 Jan 2020 15:19:31 +0100 Subject: [PATCH] End to end encryption completed through HTTP! --- .prettierrc | 2 +- src/routes.js | 221 +++++++++++++++++++++++++------------ src/server.js | 227 +++++++++++++++++++++++---------------- utils/encryptionStore.js | 108 ++++++++++++++++--- utils/protectedRoutes.js | 6 +- yarn.lock | 9 +- 6 files changed, 389 insertions(+), 184 deletions(-) diff --git a/.prettierrc b/.prettierrc index adbf2efe..b6283b83 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,5 +2,5 @@ "requirePragma": true, "semi": false, "singleQuote": true, - "endOfLine": "lf" + "endOfLine": "auto" } diff --git a/src/routes.js b/src/routes.js index b7021538..f0db22d6 100644 --- a/src/routes.js +++ b/src/routes.js @@ -10,14 +10,17 @@ const Crypto = require("crypto"); const logger = require("winston"); const httpsAgent = require("https"); const responseTime = require("response-time"); +const uuid = require("uuid/v4"); const getListPage = require("../utils/paginate"); const auth = require("../services/auth/auth"); const FS = require("../utils/fs"); +const Encryption = require("../utils/encryptionStore"); const LightningServices = require("../utils/lightningServices"); const GunDB = require("../services/gunDB/Mediator"); -const { unprotectedRoutes } = require("../utils/protectedRoutes"); +const { unprotectedRoutes, nonEncryptedRoutes } = require("../utils/protectedRoutes"); const DEFAULT_MAX_NUM_ROUTES_TO_QUERY = 10; +const SESSION_ID = uuid(); // module.exports = (app) => { module.exports = async ( @@ -116,7 +119,7 @@ module.exports = async ( const health = await checkHealth(); if (health.LNDStatus.success) { if (err) { - res.send({ + res.json({ errorMessage: sanitizeLNDError(err.message) }); } else { @@ -124,7 +127,7 @@ module.exports = async ( } } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } }; @@ -192,6 +195,53 @@ module.exports = async ( } }; + app.use((req, res, next) => { + res.setHeader("x-session-id", SESSION_ID) + next() + }) + + app.use((req, res, next) => { + const deviceId = req.headers["x-shockwallet-device-id"]; + try { + if (nonEncryptedRoutes.includes(req.path)) { + return next(); + } + + if (!deviceId) { + const error = { + field: "deviceId", + message: "Please specify a device ID" + }; + console.error(error) + return res.status(401).json(error); + } + + if (!Encryption.isAuthorizedDevice({ deviceId })) { + const error = { + field: "deviceId", + message: "Please specify a device ID" + }; + console.error("Unknown Device", error) + return res.status(401).json(error); + } + + console.log("Body:", req.body) + console.log("Decrypt params:", { deviceId, message: req.body.encryptionKey }) + const decryptedKey = Encryption.decryptKey({ deviceId, message: req.body.encryptionKey }); + console.log("decryptedKey", decryptedKey) + const decryptedMessage = Encryption.decryptMessage({ message: req.body.data, key: decryptedKey, iv: req.body.iv }) + req.body = JSON.parse(decryptedMessage); + return next(); + } catch (err) { + console.error(err); + return res + .status(401) + .json( + err + ); + } + }) + app.use(async (req, res, next) => { try { console.log("Route:", req.path) @@ -247,7 +297,7 @@ module.exports = async ( */ app.get("/health", async (req, res) => { const health = await checkHealth(); - res.send(health); + res.json(health); }); /** @@ -255,11 +305,11 @@ module.exports = async ( */ app.get("/healthz", async (req, res) => { const health = await checkHealth(); - res.send(health); + res.json(health); }); app.get("/ping", (req, res) => { - res.send("OK"); + res.json({ message: "OK" }); }); app.post("/api/mobile/error", (req, res) => { @@ -267,8 +317,36 @@ module.exports = async ( res.json({ msg: "OK" }); }); - app.post("/api/security/exchangeKeys", (req, res) => { + app.post("/api/security/exchangeKeys", async (req, res) => { + try { + const { publicKey, deviceId } = req.body; + + if (!publicKey || publicKey.length < 600) { + return res.status(400).json({ + field: 'publicKey', + message: "Please provide a valid public key" + }) + } + if (!deviceId || + !/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/iu + .test(deviceId)) { + return res.status(400).json({ + field: 'deviceId', + message: "Please provide a valid device ID" + }) + } + + const authorizedDevice = await Encryption.authorizeDevice({ deviceId, publicKey }) + console.log(authorizedDevice) + return res.status(200).json(authorizedDevice) + } catch (err) { + console.error(err) + return res.status(401).json({ + field: 'unknown', + message: err + }) + } }) app.get("/api/lnd/wallet/status", async (req, res) => { @@ -292,6 +370,7 @@ module.exports = async ( app.post("/api/lnd/auth", async (req, res) => { try { + console.log("/api/lnd/auth Body:", req.body) const health = await checkHealth(); const walletInitialized = await walletExists(); // If we're connected to lnd, unlock the wallet using the password supplied @@ -337,7 +416,7 @@ module.exports = async ( } res.status(500); - res.send({ + res.json({ field: "health", errorMessage: sanitizeLNDError(health.LNDStatus.message), success: false @@ -346,7 +425,7 @@ module.exports = async ( } catch (err) { logger.debug("Unlock Error:", err); res.status(400); - res.send({ field: "user", errorMessage: sanitizeLNDError(err.message), success: false }); + res.json({ field: "user", errorMessage: sanitizeLNDError(err.message), success: false }); return err; } }); @@ -368,10 +447,10 @@ module.exports = async ( const health = await checkHealth(); if (health.LNDStatus.success) { res.status(400); - res.send({ field: "WalletUnlocker", errorMessage: unlockErr.message }); + res.json({ field: "WalletUnlocker", errorMessage: unlockErr.message }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } else { await recreateLnServices(); @@ -435,12 +514,12 @@ module.exports = async ( const message = genSeedErr.details; return res .status(400) - .send({ field: "GenSeed", errorMessage: message, success: false }); + .json({ field: "GenSeed", errorMessage: message, success: false }); } return res .status(500) - .send({ field: "health", errorMessage: "LND is down", success: false }); + .json({ field: "health", errorMessage: "LND is down", success: false }); } logger.debug("GenSeed:", genSeedResponse); @@ -613,13 +692,13 @@ module.exports = async ( logger.error("GetInfo Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "getInfo", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.info("GetInfo:", response); @@ -644,13 +723,13 @@ module.exports = async ( const health = await checkHealth(); if (health.LNDStatus.success) { res.status(400); - res.send({ + res.json({ field: "getNodeInfo", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("GetNodeInfo:", response); @@ -666,13 +745,13 @@ module.exports = async ( logger.debug("GetNetworkInfo Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "getNodeInfo", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("GetNetworkInfo:", response); @@ -688,13 +767,13 @@ module.exports = async ( logger.debug("ListPeers Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "listPeers", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("ListPeers:", response); @@ -710,13 +789,13 @@ module.exports = async ( logger.debug("NewAddress Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "newAddress", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("NewAddress:", response); @@ -731,13 +810,13 @@ module.exports = async ( const health = await checkHealth(); if (health.LNDStatus.success) { res.status(403); - return res.send({ + return res.json({ field: "limituser", errorMessage: "User limited" }); } res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } const connectRequest = { addr: { pubkey: req.body.pubkey, host: req.body.host }, @@ -747,7 +826,7 @@ module.exports = async ( lightning.connectPeer(connectRequest, (err, response) => { if (err) { logger.debug("ConnectPeer Error:", err); - res.status(500).send({ field: "connectPeer", errorMessage: sanitizeLNDError(err.message) }); + res.status(500).json({ field: "connectPeer", errorMessage: sanitizeLNDError(err.message) }); } else { logger.debug("ConnectPeer:", response); res.json(response); @@ -762,20 +841,20 @@ module.exports = async ( const health = await checkHealth(); if (health.LNDStatus.success) { res.status(403); - return res.send({ + return res.json({ field: "limituser", errorMessage: "User limited" }); } res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } const disconnectRequest = { pub_key: req.body.pubkey }; logger.debug("DisconnectPeer Request:", disconnectRequest); lightning.disconnectPeer(disconnectRequest, (err, response) => { if (err) { logger.debug("DisconnectPeer Error:", err); - res.status(400).send({ field: "disconnectPeer", errorMessage: sanitizeLNDError(err.message) }); + res.status(400).json({ field: "disconnectPeer", errorMessage: sanitizeLNDError(err.message) }); } else { logger.debug("DisconnectPeer:", response); res.json(response); @@ -791,13 +870,13 @@ module.exports = async ( logger.debug("ListChannels Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "listChannels", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("ListChannels:", response); @@ -813,13 +892,13 @@ module.exports = async ( logger.debug("PendingChannels Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "pendingChannels", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("PendingChannels:", response); @@ -906,10 +985,10 @@ module.exports = async ( logger.debug("ListInvoices Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ errorMessage: sanitizeLNDError(err.message), success: false }); + res.status(400).json({ errorMessage: sanitizeLNDError(err.message), success: false }); } else { res.status(500); - res.send({ errorMessage: health.LNDStatus.message, success: false }); + res.json({ errorMessage: health.LNDStatus.message, success: false }); } } else { // logger.debug("ListInvoices:", response); @@ -932,13 +1011,13 @@ module.exports = async ( logger.debug("ForwardingHistory Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "forwardingHistory", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("ForwardingHistory:", response); @@ -954,13 +1033,13 @@ module.exports = async ( logger.debug("WalletBalance Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "walletBalance", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("WalletBalance:", response); @@ -976,13 +1055,13 @@ module.exports = async ( if (err) { logger.debug("WalletBalance Error:", err); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "walletBalance", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send(health.LNDStatus); + res.json(health.LNDStatus); } return err; } @@ -991,13 +1070,13 @@ module.exports = async ( if (err) { logger.debug("ChannelBalance Error:", err); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "channelBalance", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send(health.LNDStatus); + res.json(health.LNDStatus); } return err; } @@ -1020,11 +1099,11 @@ module.exports = async ( logger.debug("DecodePayReq Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(500).send({ + res.status(500).json({ errorMessage: sanitizeLNDError(err.message) }); } else { - res.status(500).send({ errorMessage: "LND is down" }); + res.status(500).json({ errorMessage: "LND is down" }); } } else { logger.info("DecodePayReq:", paymentRequest); @@ -1042,13 +1121,13 @@ module.exports = async ( logger.debug("ChannelBalance Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "channelBalance", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("ChannelBalance:", response); @@ -1065,7 +1144,7 @@ module.exports = async ( res.sendStatus(403); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } return; } @@ -1089,10 +1168,10 @@ module.exports = async ( logger.info("OpenChannelRequest Error:", err); const health = await checkHealth(); if (health.LNDStatus.success && !res.headersSent) { - res.status(500).send({ field: "openChannelRequest", errorMessage: sanitizeLNDError(err.message) }); + res.status(500).json({ field: "openChannelRequest", errorMessage: sanitizeLNDError(err.message) }); } else if (!res.headersSent) { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } }); openedChannel.write(openChannelRequest) @@ -1108,7 +1187,7 @@ module.exports = async ( res.sendStatus(403); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } const { channelPoint, outputIndex } = req.body; @@ -1136,13 +1215,13 @@ module.exports = async ( if (!res.headersSent) { if (health.LNDStatus.success) { logger.debug("CloseChannelRequest Error:", err); - res.status(400).send({ + res.status(400).json({ field: "closeChannel", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } }); @@ -1157,7 +1236,7 @@ module.exports = async ( res.sendStatus(403); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } const paymentRequest = { payment_request: req.body.payreq }; @@ -1189,12 +1268,12 @@ module.exports = async ( logger.error("SendPayment Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(500).send({ + res.status(500).json({ errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } }); @@ -1210,7 +1289,7 @@ module.exports = async ( res.sendStatus(403); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } return false; } @@ -1226,13 +1305,13 @@ module.exports = async ( logger.debug("AddInvoice Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "addInvoice", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } return err; } @@ -1250,7 +1329,7 @@ module.exports = async ( res.sendStatus(403); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } lightning.signMessage( @@ -1260,10 +1339,10 @@ module.exports = async ( logger.debug("SignMessage Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ field: "signMessage", errorMessage: sanitizeLNDError(err.message) }); + res.status(400).json({ field: "signMessage", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("SignMessage:", response); @@ -1282,10 +1361,10 @@ module.exports = async ( logger.debug("VerifyMessage Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ field: "verifyMessage", errorMessage: sanitizeLNDError(err.message) }); + res.status(400).json({ field: "verifyMessage", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("VerifyMessage:", response); @@ -1303,7 +1382,7 @@ module.exports = async ( res.sendStatus(403); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } const sendCoinsRequest = { addr: req.body.addr, amount: req.body.amount }; @@ -1313,13 +1392,13 @@ module.exports = async ( logger.debug("SendCoins Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "sendCoins", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("SendCoins:", response); @@ -1339,10 +1418,10 @@ module.exports = async ( logger.debug("QueryRoute Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ field: "queryRoute", errorMessage: sanitizeLNDError(err.message) }); + res.status(400).json({ field: "queryRoute", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("QueryRoute:", response); @@ -1365,12 +1444,12 @@ module.exports = async ( if (err) { const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ error: err.message }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } else { logger.debug("EstimateFee:", fee); diff --git a/src/server.js b/src/server.js index 3743f2cc..f0f1dbe7 100644 --- a/src/server.js +++ b/src/server.js @@ -1,78 +1,119 @@ -"use strict"; +/** + * @prettier + */ /** * Module dependencies. */ const server = program => { - const Https = require("https"); - const Http = require("http"); - const Express = require("express"); - const LightningServices = require("../utils/lightningServices"); - const app = Express(); - - const FS = require("../utils/fs"); - const bodyParser = require("body-parser"); - const session = require("express-session"); - const methodOverride = require("method-override"); - const { unprotectedRoutes, sensitiveRoutes } = require("../utils/protectedRoutes"); + const Https = require('https') + const Http = require('http') + const Express = require('express') + const LightningServices = require('../utils/lightningServices') + const Encryption = require('../utils/encryptionStore') + const app = Express() + + const FS = require('../utils/fs') + const bodyParser = require('body-parser') + const session = require('express-session') + const methodOverride = require('method-override') + const { + unprotectedRoutes, + sensitiveRoutes, + nonEncryptedRoutes + } = require('../utils/protectedRoutes') // load app default configuration data - const defaults = require("../config/defaults")(program.mainnet); + const defaults = require('../config/defaults')(program.mainnet) // define useful global variables ====================================== - module.useTLS = program.usetls; - module.serverPort = program.serverport || defaults.serverPort; - module.httpsPort = module.serverPort; - module.serverHost = program.serverhost || defaults.serverHost; + module.useTLS = program.usetls + module.serverPort = program.serverport || defaults.serverPort + module.httpsPort = module.serverPort + module.serverHost = program.serverhost || defaults.serverHost // setup winston logging ========== - const logger = require("../config/log")( + const logger = require('../config/log')( program.logfile || defaults.logfile, program.loglevel || defaults.loglevel - ); + ) // utilities functions ================= - require("../utils/server-utils")(module); + require('../utils/server-utils')(module) - logger.info("Mainnet Mode:", !!program.mainnet); + logger.info('Mainnet Mode:', !!program.mainnet) + + const modifyResponseBody = (req, res, next) => { + const deviceId = req.headers['x-shockwallet-device-id'] + const oldSend = res.send + + if (!nonEncryptedRoutes.includes(req.path)) { + res.send = (...args) => { + if (args[0] && args[0].encryptedData && args[0].encryptionKey) { + console.log('Response loop detected', req.path, args[0]) + oldSend.apply(res, args) + } else { + // arguments[0] (or `data`) contains the response body + const authorized = Encryption.isAuthorizedDevice({ deviceId }) + const encryptedMessage = authorized + ? Encryption.encryptMessage({ + message: args[0], + deviceId + }) + : args[0] + args[0] = JSON.stringify(encryptedMessage) + oldSend.apply(res, args) + } + } + } + next() + } const wait = seconds => new Promise(resolve => { - const timer = setTimeout(() => resolve(timer), seconds * 1000); - }); + const timer = setTimeout(() => resolve(timer), seconds * 1000) + }) // eslint-disable-next-line consistent-return const startServer = async () => { try { - LightningServices.setDefaults(program); - await LightningServices.init(); + LightningServices.setDefaults(program) + await LightningServices.init() // init lnd module ================= - const lnd = require("../services/lnd/lnd")(LightningServices.services.lightning); - const auth = require("../services/auth/auth"); + const lnd = require('../services/lnd/lnd')( + LightningServices.services.lightning + ) + const auth = require('../services/auth/auth') app.use(async (req, res, next) => { - console.log("Route:", req.path) + console.log('Route:', req.path) if (unprotectedRoutes[req.method][req.path]) { - next(); + next() } else { try { const response = await auth.validateToken( - req.headers.authorization.replace("Bearer ", "") - ); + req.headers.authorization.replace('Bearer ', '') + ) if (response.valid) { - next(); + next() } else { - res.status(401).json({ field: "authorization", errorMessage: "The authorization token you've supplied is invalid" }); + res.status(401).json({ + field: 'authorization', + errorMessage: + "The authorization token you've supplied is invalid" + }) } } catch (err) { logger.error( - !req.headers.authorization - ? "Please add an Authorization header" + !req.headers.authorization + ? 'Please add an Authorization header' : err - ); - res.status(401).json({ field: "authorization", errorMessage: "Please log in" }); + ) + res + .status(401) + .json({ field: 'authorization', errorMessage: 'Please log in' }) } } - }); + }) app.use((req, res, next) => { if (sensitiveRoutes[req.method][req.path]) { @@ -84,7 +125,7 @@ const server = program => { path: req.path, sessionId: req.sessionId }) - ); + ) } else { console.log( JSON.stringify({ @@ -96,10 +137,10 @@ const server = program => { query: req.query, sessionId: req.sessionId }) - ); + ) } - next(); - }); + next() + }) app.use( session({ secret: defaults.sessionSecret, @@ -108,25 +149,23 @@ const server = program => { rolling: true, saveUninitialized: true }) - ); - app.use(bodyParser.urlencoded({ extended: "true" })); - app.use(bodyParser.json()); - app.use(bodyParser.json({ type: "application/vnd.api+json" })); - app.use(methodOverride()); + ) + app.use(bodyParser.urlencoded({ extended: 'true' })) + app.use(bodyParser.json()) + app.use(bodyParser.json({ type: 'application/vnd.api+json' })) + app.use(methodOverride()) // WARNING // error handler middleware, KEEP 4 parameters as express detects the // arity of the function to treat it as a err handling middleware // eslint-disable-next-line no-unused-vars app.use((err, _, res, __) => { // Do logging and user-friendly error message display - logger.error(err); - res - .status(500) - .send({ status: 500, errorMessage: "internal error" }); - }); + logger.error(err) + res.status(500).send({ status: 500, errorMessage: 'internal error' }) + }) const CA = LightningServices.servicesConfig.lndCertPath - const CA_KEY = CA.replace("cert", "key") + const CA_KEY = CA.replace('cert', 'key') const createServer = async () => { try { @@ -134,62 +173,60 @@ const server = program => { const [key, cert] = await Promise.all([ FS.readFile(CA_KEY), FS.readFile(CA) - ]); - const httpsServer = Https.createServer({ key, cert }, app); + ]) + const httpsServer = Https.createServer({ key, cert }, app) - return httpsServer; + return httpsServer } - const httpServer = Http.Server(app); - return httpServer; + const httpServer = Http.Server(app) + return httpServer } catch (err) { - logger.error(err.message); - logger.error("An error has occurred while finding an LND cert to use to open an HTTPS server") - logger.warn("Falling back to opening an HTTP server...") - const httpServer = Http.Server(app); + logger.error(err.message) + logger.error( + 'An error has occurred while finding an LND cert to use to open an HTTPS server' + ) + logger.warn('Falling back to opening an HTTP server...') + const httpServer = Http.Server(app) return httpServer } - }; + } - const serverInstance = await createServer(); + const serverInstance = await createServer() - const io = require("socket.io")(serverInstance); + const io = require('socket.io')(serverInstance) - const Sockets = require("./sockets")( + const Sockets = require('./sockets')( io, lnd, program.user, program.pwd, program.limituser, program.limitpwd - ); + ) - require("./routes")( - app, - defaults, - Sockets, - { - serverHost: module.serverHost, - serverPort: module.serverPort, - usetls: program.usetls, - CA, - CA_KEY - } - ); + require('./routes')(app, defaults, Sockets, { + serverHost: module.serverHost, + serverPort: module.serverPort, + usetls: program.usetls, + CA, + CA_KEY + }) // enable CORS headers - app.use(require("./cors")); + app.use(require('./cors')) // app.use(bodyParser.json({limit: '100000mb'})); - app.use(bodyParser.json({ limit: "50mb" })); - app.use(bodyParser.urlencoded({ limit: "50mb", extended: true })); + app.use(bodyParser.json({ limit: '50mb' })) + app.use(bodyParser.urlencoded({ limit: '50mb', extended: true })) + app.use(modifyResponseBody) - serverInstance.listen(module.serverPort, module.serverhost); + serverInstance.listen(module.serverPort, module.serverhost) logger.info( - "App listening on " + module.serverHost + " port " + module.serverPort - ); + 'App listening on ' + module.serverHost + ' port ' + module.serverPort + ) - module.server = serverInstance; + module.server = serverInstance // const localtunnel = require('localtunnel'); // @@ -198,15 +235,15 @@ const server = program => { // console.log('t', t.url); // }); } catch (err) { - logger.info(err); - logger.info("Restarting server in 30 seconds..."); - await wait(30); - startServer(); - return false; + logger.info(err) + logger.info('Restarting server in 30 seconds...') + await wait(30) + startServer() + return false } - }; + } - startServer(); -}; + startServer() +} -module.exports = server; +module.exports = server diff --git a/utils/encryptionStore.js b/utils/encryptionStore.js index 3e5c75f4..026d381e 100644 --- a/utils/encryptionStore.js +++ b/utils/encryptionStore.js @@ -6,52 +6,130 @@ const { Buffer } = require('buffer') const APIKeyPair = new Map() const authorizedDevices = new Map() -module.exports = { - encrypt: ({ deviceId, message }) => { +const Encryption = { + encryptKey: ({ deviceId, message }) => { if (!authorizedDevices.has(deviceId)) { throw { field: 'deviceId', message: 'Unknown Device ID' } } const devicePublicKey = authorizedDevices.get(deviceId) const data = Buffer.from(message) - const encryptedData = Crypto.publicEncrypt(devicePublicKey, data) + const encryptedData = Crypto.publicEncrypt( + { + key: devicePublicKey, + padding: Crypto.constants.RSA_PKCS1_PADDING + }, + data + ) return encryptedData.toString('base64') }, - decrypt: ({ deviceId, message }) => { + decryptKey: ({ deviceId, message }) => { if (!authorizedDevices.has(deviceId)) { throw { field: 'deviceId', message: 'Unknown Device ID' } } const data = Buffer.from(message, 'base64') - const encryptedData = Crypto.privateDecrypt(APIKeyPair.private, data) - return encryptedData.toString('base64') + const encryptedData = Crypto.privateDecrypt( + { + key: APIKeyPair.get(deviceId).privateKey, + padding: Crypto.constants.RSA_PKCS1_PADDING + }, + data + ) + console.log('Decrypted Data:', encryptedData) + return encryptedData.toString() + }, + encryptMessage: ({ deviceId, message }) => { + const parsedMessage = + typeof message === 'object' ? JSON.stringify(message) : message + const data = Buffer.from(parsedMessage) + const key = Crypto.randomBytes(32) + const iv = Crypto.randomBytes(16) + const encryptedKey = Encryption.encryptKey({ + deviceId, + message: key.toString('hex') + }) + const cipher = Crypto.createCipheriv('aes-256-cbc', key, iv) + const encryptedCipher = cipher.update(data) + const encryptedBuffer = Buffer.concat([ + Buffer.from(encryptedCipher), + Buffer.from(cipher.final()) + ]) + const encryptedData = encryptedBuffer.toString('base64') + return { encryptedData, encryptedKey, iv: iv.toString('hex') } + }, + decryptMessage: ({ message, key, iv }) => { + const data = Buffer.from(message, 'base64') + const cipher = Crypto.createDecipheriv( + 'aes-256-cbc', + Buffer.from(key, 'hex'), + Buffer.from(iv, 'hex') + ) + const decryptedCipher = cipher.update(data) + const decryptedBuffer = Buffer.concat([ + Buffer.from(decryptedCipher), + Buffer.from(cipher.final()) + ]) + const decryptedData = decryptedBuffer.toString() + console.log('Decrypted Data:', decryptedData) + return decryptedData.toString() + }, + isAuthorizedDevice: ({ deviceId }) => { + console.log( + 'deviceId', + deviceId, + Object.fromEntries(authorizedDevices.entries()), + authorizedDevices.has(deviceId) + ) + if (authorizedDevices.has(deviceId)) { + return true + } + + return false }, authorizeDevice: ({ deviceId, publicKey }) => new Promise((resolve, reject) => { if (authorizedDevices.has(deviceId)) { - const error = { success: false, message: 'Device already exists' } - reject(error) - return error + const devicePublicKey = APIKeyPair.get(deviceId).publicKey + const deviceExists = { + success: true, + APIPublicKey: devicePublicKey + } + resolve(deviceExists) + return deviceExists } authorizedDevices.set(deviceId, publicKey) - Crypto.generateKeyPair( 'rsa', { - modulusLength: 4096 + modulusLength: 4096, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem' + }, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + } }, (err, publicKey, privateKey) => { if (err) { - reject({ field: 'APIKeyPair', errorMessage: err }) + console.error(err) + reject(err) return err } - APIKeyPair.set(deviceId, { + const exportedKey = { publicKey, privateKey + } + + APIKeyPair.set(deviceId, exportedKey) + resolve({ + success: true, + APIPublicKey: exportedKey.publicKey }) - resolve({ success: true }) } ) }), @@ -59,3 +137,5 @@ module.exports = { authorizedDevices.delete(deviceId) } } + +module.exports = Encryption diff --git a/utils/protectedRoutes.js b/utils/protectedRoutes.js index a0b53ca7..8ece4f12 100644 --- a/utils/protectedRoutes.js +++ b/utils/protectedRoutes.js @@ -13,7 +13,8 @@ module.exports = { "/api/lnd/connect": true, "/api/lnd/wallet": true, "/api/lnd/wallet/existing": true, - "/api/lnd/auth": true + "/api/lnd/auth": true, + "/api/security/exchangeKeys": true }, PUT: {}, DELETE: {} @@ -26,5 +27,6 @@ module.exports = { }, PUT: {}, DELETE: {} - } + }, + nonEncryptedRoutes: ['/api/security/exchangeKeys', '/healthz', '/ping', '/api/lnd/wallet/status'] } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 77699c0c..7abb87b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -873,7 +873,7 @@ ascli@~1: colour "~0.7.1" optjs "~3.2.2" -asn1@~0.2.3: +asn1@^0.2.4, asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== @@ -4341,6 +4341,13 @@ node-pre-gyp@^0.13.0: semver "^5.3.0" tar "^4" +node-rsa@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/node-rsa/-/node-rsa-1.0.7.tgz#85b7a6d6fa8ee624be6402a6b41be49272d58055" + integrity sha512-idwRXma6scFufZmbaKkHpJoLL93yynRefP6yur13wZ5i9FR35ex451KCoF2OORDeJanyRVahmjjiwmUlCnTqJA== + dependencies: + asn1 "^0.2.4" + nodemon@^1.19.3: version "1.19.3" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.19.3.tgz#db71b3e62aef2a8e1283a9fa00164237356102c0"