End to end encryption completed through HTTP!

This commit is contained in:
emad-salah 2020-01-20 15:19:31 +01:00
parent 2a604edb9e
commit 4126fd503a
6 changed files with 389 additions and 184 deletions

View file

@ -2,5 +2,5 @@
"requirePragma": true, "requirePragma": true,
"semi": false, "semi": false,
"singleQuote": true, "singleQuote": true,
"endOfLine": "lf" "endOfLine": "auto"
} }

View file

@ -10,14 +10,17 @@ const Crypto = require("crypto");
const logger = require("winston"); const logger = require("winston");
const httpsAgent = require("https"); const httpsAgent = require("https");
const responseTime = require("response-time"); const responseTime = require("response-time");
const uuid = require("uuid/v4");
const getListPage = require("../utils/paginate"); const getListPage = require("../utils/paginate");
const auth = require("../services/auth/auth"); const auth = require("../services/auth/auth");
const FS = require("../utils/fs"); const FS = require("../utils/fs");
const Encryption = require("../utils/encryptionStore");
const LightningServices = require("../utils/lightningServices"); const LightningServices = require("../utils/lightningServices");
const GunDB = require("../services/gunDB/Mediator"); 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 DEFAULT_MAX_NUM_ROUTES_TO_QUERY = 10;
const SESSION_ID = uuid();
// module.exports = (app) => { // module.exports = (app) => {
module.exports = async ( module.exports = async (
@ -116,7 +119,7 @@ module.exports = async (
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
if (err) { if (err) {
res.send({ res.json({
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
@ -124,7 +127,7 @@ module.exports = async (
} }
} else { } else {
res.status(500); 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) => { app.use(async (req, res, next) => {
try { try {
console.log("Route:", req.path) console.log("Route:", req.path)
@ -247,7 +297,7 @@ module.exports = async (
*/ */
app.get("/health", async (req, res) => { app.get("/health", async (req, res) => {
const health = await checkHealth(); const health = await checkHealth();
res.send(health); res.json(health);
}); });
/** /**
@ -255,11 +305,11 @@ module.exports = async (
*/ */
app.get("/healthz", async (req, res) => { app.get("/healthz", async (req, res) => {
const health = await checkHealth(); const health = await checkHealth();
res.send(health); res.json(health);
}); });
app.get("/ping", (req, res) => { app.get("/ping", (req, res) => {
res.send("OK"); res.json({ message: "OK" });
}); });
app.post("/api/mobile/error", (req, res) => { app.post("/api/mobile/error", (req, res) => {
@ -267,8 +317,36 @@ module.exports = async (
res.json({ msg: "OK" }); 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) => { app.get("/api/lnd/wallet/status", async (req, res) => {
@ -292,6 +370,7 @@ module.exports = async (
app.post("/api/lnd/auth", async (req, res) => { app.post("/api/lnd/auth", async (req, res) => {
try { try {
console.log("/api/lnd/auth Body:", req.body)
const health = await checkHealth(); const health = await checkHealth();
const walletInitialized = await walletExists(); const walletInitialized = await walletExists();
// If we're connected to lnd, unlock the wallet using the password supplied // If we're connected to lnd, unlock the wallet using the password supplied
@ -337,7 +416,7 @@ module.exports = async (
} }
res.status(500); res.status(500);
res.send({ res.json({
field: "health", field: "health",
errorMessage: sanitizeLNDError(health.LNDStatus.message), errorMessage: sanitizeLNDError(health.LNDStatus.message),
success: false success: false
@ -346,7 +425,7 @@ module.exports = async (
} catch (err) { } catch (err) {
logger.debug("Unlock Error:", err); logger.debug("Unlock Error:", err);
res.status(400); 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; return err;
} }
}); });
@ -368,10 +447,10 @@ module.exports = async (
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(400); res.status(400);
res.send({ field: "WalletUnlocker", errorMessage: unlockErr.message }); res.json({ field: "WalletUnlocker", errorMessage: unlockErr.message });
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} else { } else {
await recreateLnServices(); await recreateLnServices();
@ -435,12 +514,12 @@ module.exports = async (
const message = genSeedErr.details; const message = genSeedErr.details;
return res return res
.status(400) .status(400)
.send({ field: "GenSeed", errorMessage: message, success: false }); .json({ field: "GenSeed", errorMessage: message, success: false });
} }
return res return res
.status(500) .status(500)
.send({ field: "health", errorMessage: "LND is down", success: false }); .json({ field: "health", errorMessage: "LND is down", success: false });
} }
logger.debug("GenSeed:", genSeedResponse); logger.debug("GenSeed:", genSeedResponse);
@ -613,13 +692,13 @@ module.exports = async (
logger.error("GetInfo Error:", err); logger.error("GetInfo Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(400).send({ res.status(400).json({
field: "getInfo", field: "getInfo",
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} }
logger.info("GetInfo:", response); logger.info("GetInfo:", response);
@ -644,13 +723,13 @@ module.exports = async (
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(400); res.status(400);
res.send({ res.json({
field: "getNodeInfo", field: "getNodeInfo",
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} }
logger.debug("GetNodeInfo:", response); logger.debug("GetNodeInfo:", response);
@ -666,13 +745,13 @@ module.exports = async (
logger.debug("GetNetworkInfo Error:", err); logger.debug("GetNetworkInfo Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(400).send({ res.status(400).json({
field: "getNodeInfo", field: "getNodeInfo",
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} }
logger.debug("GetNetworkInfo:", response); logger.debug("GetNetworkInfo:", response);
@ -688,13 +767,13 @@ module.exports = async (
logger.debug("ListPeers Error:", err); logger.debug("ListPeers Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(400).send({ res.status(400).json({
field: "listPeers", field: "listPeers",
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} }
logger.debug("ListPeers:", response); logger.debug("ListPeers:", response);
@ -710,13 +789,13 @@ module.exports = async (
logger.debug("NewAddress Error:", err); logger.debug("NewAddress Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(400).send({ res.status(400).json({
field: "newAddress", field: "newAddress",
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} }
logger.debug("NewAddress:", response); logger.debug("NewAddress:", response);
@ -731,13 +810,13 @@ module.exports = async (
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(403); res.status(403);
return res.send({ return res.json({
field: "limituser", field: "limituser",
errorMessage: "User limited" errorMessage: "User limited"
}); });
} }
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
const connectRequest = { const connectRequest = {
addr: { pubkey: req.body.pubkey, host: req.body.host }, addr: { pubkey: req.body.pubkey, host: req.body.host },
@ -747,7 +826,7 @@ module.exports = async (
lightning.connectPeer(connectRequest, (err, response) => { lightning.connectPeer(connectRequest, (err, response) => {
if (err) { if (err) {
logger.debug("ConnectPeer Error:", 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 { } else {
logger.debug("ConnectPeer:", response); logger.debug("ConnectPeer:", response);
res.json(response); res.json(response);
@ -762,20 +841,20 @@ module.exports = async (
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(403); res.status(403);
return res.send({ return res.json({
field: "limituser", field: "limituser",
errorMessage: "User limited" errorMessage: "User limited"
}); });
} }
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
const disconnectRequest = { pub_key: req.body.pubkey }; const disconnectRequest = { pub_key: req.body.pubkey };
logger.debug("DisconnectPeer Request:", disconnectRequest); logger.debug("DisconnectPeer Request:", disconnectRequest);
lightning.disconnectPeer(disconnectRequest, (err, response) => { lightning.disconnectPeer(disconnectRequest, (err, response) => {
if (err) { if (err) {
logger.debug("DisconnectPeer Error:", 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 { } else {
logger.debug("DisconnectPeer:", response); logger.debug("DisconnectPeer:", response);
res.json(response); res.json(response);
@ -791,13 +870,13 @@ module.exports = async (
logger.debug("ListChannels Error:", err); logger.debug("ListChannels Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(400).send({ res.status(400).json({
field: "listChannels", field: "listChannels",
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} }
logger.debug("ListChannels:", response); logger.debug("ListChannels:", response);
@ -813,13 +892,13 @@ module.exports = async (
logger.debug("PendingChannels Error:", err); logger.debug("PendingChannels Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(400).send({ res.status(400).json({
field: "pendingChannels", field: "pendingChannels",
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} }
logger.debug("PendingChannels:", response); logger.debug("PendingChannels:", response);
@ -906,10 +985,10 @@ module.exports = async (
logger.debug("ListInvoices Error:", err); logger.debug("ListInvoices Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { 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 { } else {
res.status(500); res.status(500);
res.send({ errorMessage: health.LNDStatus.message, success: false }); res.json({ errorMessage: health.LNDStatus.message, success: false });
} }
} else { } else {
// logger.debug("ListInvoices:", response); // logger.debug("ListInvoices:", response);
@ -932,13 +1011,13 @@ module.exports = async (
logger.debug("ForwardingHistory Error:", err); logger.debug("ForwardingHistory Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(400).send({ res.status(400).json({
field: "forwardingHistory", field: "forwardingHistory",
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} }
logger.debug("ForwardingHistory:", response); logger.debug("ForwardingHistory:", response);
@ -954,13 +1033,13 @@ module.exports = async (
logger.debug("WalletBalance Error:", err); logger.debug("WalletBalance Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(400).send({ res.status(400).json({
field: "walletBalance", field: "walletBalance",
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} }
logger.debug("WalletBalance:", response); logger.debug("WalletBalance:", response);
@ -976,13 +1055,13 @@ module.exports = async (
if (err) { if (err) {
logger.debug("WalletBalance Error:", err); logger.debug("WalletBalance Error:", err);
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(400).send({ res.status(400).json({
field: "walletBalance", field: "walletBalance",
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
res.status(500); res.status(500);
res.send(health.LNDStatus); res.json(health.LNDStatus);
} }
return err; return err;
} }
@ -991,13 +1070,13 @@ module.exports = async (
if (err) { if (err) {
logger.debug("ChannelBalance Error:", err); logger.debug("ChannelBalance Error:", err);
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(400).send({ res.status(400).json({
field: "channelBalance", field: "channelBalance",
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
res.status(500); res.status(500);
res.send(health.LNDStatus); res.json(health.LNDStatus);
} }
return err; return err;
} }
@ -1020,11 +1099,11 @@ module.exports = async (
logger.debug("DecodePayReq Error:", err); logger.debug("DecodePayReq Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(500).send({ res.status(500).json({
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
res.status(500).send({ errorMessage: "LND is down" }); res.status(500).json({ errorMessage: "LND is down" });
} }
} else { } else {
logger.info("DecodePayReq:", paymentRequest); logger.info("DecodePayReq:", paymentRequest);
@ -1042,13 +1121,13 @@ module.exports = async (
logger.debug("ChannelBalance Error:", err); logger.debug("ChannelBalance Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(400).send({ res.status(400).json({
field: "channelBalance", field: "channelBalance",
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} }
logger.debug("ChannelBalance:", response); logger.debug("ChannelBalance:", response);
@ -1065,7 +1144,7 @@ module.exports = async (
res.sendStatus(403); res.sendStatus(403);
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
return; return;
} }
@ -1089,10 +1168,10 @@ module.exports = async (
logger.info("OpenChannelRequest Error:", err); logger.info("OpenChannelRequest Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success && !res.headersSent) { 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) { } else if (!res.headersSent) {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
}); });
openedChannel.write(openChannelRequest) openedChannel.write(openChannelRequest)
@ -1108,7 +1187,7 @@ module.exports = async (
res.sendStatus(403); res.sendStatus(403);
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} }
const { channelPoint, outputIndex } = req.body; const { channelPoint, outputIndex } = req.body;
@ -1136,13 +1215,13 @@ module.exports = async (
if (!res.headersSent) { if (!res.headersSent) {
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
logger.debug("CloseChannelRequest Error:", err); logger.debug("CloseChannelRequest Error:", err);
res.status(400).send({ res.status(400).json({
field: "closeChannel", field: "closeChannel",
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
res.status(500); 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); res.sendStatus(403);
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} }
const paymentRequest = { payment_request: req.body.payreq }; const paymentRequest = { payment_request: req.body.payreq };
@ -1189,12 +1268,12 @@ module.exports = async (
logger.error("SendPayment Error:", err); logger.error("SendPayment Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(500).send({ res.status(500).json({
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
res.status(500); 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); res.sendStatus(403);
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
return false; return false;
} }
@ -1226,13 +1305,13 @@ module.exports = async (
logger.debug("AddInvoice Error:", err); logger.debug("AddInvoice Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(400).send({ res.status(400).json({
field: "addInvoice", field: "addInvoice",
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
return err; return err;
} }
@ -1250,7 +1329,7 @@ module.exports = async (
res.sendStatus(403); res.sendStatus(403);
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} }
lightning.signMessage( lightning.signMessage(
@ -1260,10 +1339,10 @@ module.exports = async (
logger.debug("SignMessage Error:", err); logger.debug("SignMessage Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { 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 { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} }
logger.debug("SignMessage:", response); logger.debug("SignMessage:", response);
@ -1282,10 +1361,10 @@ module.exports = async (
logger.debug("VerifyMessage Error:", err); logger.debug("VerifyMessage Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { 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 { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} }
logger.debug("VerifyMessage:", response); logger.debug("VerifyMessage:", response);
@ -1303,7 +1382,7 @@ module.exports = async (
res.sendStatus(403); res.sendStatus(403);
} else { } else {
res.status(500); 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 }; const sendCoinsRequest = { addr: req.body.addr, amount: req.body.amount };
@ -1313,13 +1392,13 @@ module.exports = async (
logger.debug("SendCoins Error:", err); logger.debug("SendCoins Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(400).send({ res.status(400).json({
field: "sendCoins", field: "sendCoins",
errorMessage: sanitizeLNDError(err.message) errorMessage: sanitizeLNDError(err.message)
}); });
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} }
logger.debug("SendCoins:", response); logger.debug("SendCoins:", response);
@ -1339,10 +1418,10 @@ module.exports = async (
logger.debug("QueryRoute Error:", err); logger.debug("QueryRoute Error:", err);
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { 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 { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} }
logger.debug("QueryRoute:", response); logger.debug("QueryRoute:", response);
@ -1365,12 +1444,12 @@ module.exports = async (
if (err) { if (err) {
const health = await checkHealth(); const health = await checkHealth();
if (health.LNDStatus.success) { if (health.LNDStatus.success) {
res.status(400).send({ res.status(400).json({
error: err.message error: err.message
}); });
} else { } else {
res.status(500); res.status(500);
res.send({ errorMessage: "LND is down" }); res.json({ errorMessage: "LND is down" });
} }
} else { } else {
logger.debug("EstimateFee:", fee); logger.debug("EstimateFee:", fee);

View file

@ -1,78 +1,119 @@
"use strict"; /**
* @prettier
*/
/** /**
* Module dependencies. * Module dependencies.
*/ */
const server = program => { const server = program => {
const Https = require("https"); const Https = require('https')
const Http = require("http"); const Http = require('http')
const Express = require("express"); const Express = require('express')
const LightningServices = require("../utils/lightningServices"); const LightningServices = require('../utils/lightningServices')
const app = Express(); const Encryption = require('../utils/encryptionStore')
const app = Express()
const FS = require("../utils/fs"); const FS = require('../utils/fs')
const bodyParser = require("body-parser"); const bodyParser = require('body-parser')
const session = require("express-session"); const session = require('express-session')
const methodOverride = require("method-override"); const methodOverride = require('method-override')
const { unprotectedRoutes, sensitiveRoutes } = require("../utils/protectedRoutes"); const {
unprotectedRoutes,
sensitiveRoutes,
nonEncryptedRoutes
} = require('../utils/protectedRoutes')
// load app default configuration data // load app default configuration data
const defaults = require("../config/defaults")(program.mainnet); const defaults = require('../config/defaults')(program.mainnet)
// define useful global variables ====================================== // define useful global variables ======================================
module.useTLS = program.usetls; module.useTLS = program.usetls
module.serverPort = program.serverport || defaults.serverPort; module.serverPort = program.serverport || defaults.serverPort
module.httpsPort = module.serverPort; module.httpsPort = module.serverPort
module.serverHost = program.serverhost || defaults.serverHost; module.serverHost = program.serverhost || defaults.serverHost
// setup winston logging ========== // setup winston logging ==========
const logger = require("../config/log")( const logger = require('../config/log')(
program.logfile || defaults.logfile, program.logfile || defaults.logfile,
program.loglevel || defaults.loglevel program.loglevel || defaults.loglevel
); )
// utilities functions ================= // 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 => const wait = seconds =>
new Promise(resolve => { new Promise(resolve => {
const timer = setTimeout(() => resolve(timer), seconds * 1000); const timer = setTimeout(() => resolve(timer), seconds * 1000)
}); })
// eslint-disable-next-line consistent-return // eslint-disable-next-line consistent-return
const startServer = async () => { const startServer = async () => {
try { try {
LightningServices.setDefaults(program); LightningServices.setDefaults(program)
await LightningServices.init(); await LightningServices.init()
// init lnd module ================= // init lnd module =================
const lnd = require("../services/lnd/lnd")(LightningServices.services.lightning); const lnd = require('../services/lnd/lnd')(
const auth = require("../services/auth/auth"); LightningServices.services.lightning
)
const auth = require('../services/auth/auth')
app.use(async (req, res, next) => { app.use(async (req, res, next) => {
console.log("Route:", req.path) console.log('Route:', req.path)
if (unprotectedRoutes[req.method][req.path]) { if (unprotectedRoutes[req.method][req.path]) {
next(); next()
} else { } else {
try { try {
const response = await auth.validateToken( const response = await auth.validateToken(
req.headers.authorization.replace("Bearer ", "") req.headers.authorization.replace('Bearer ', '')
); )
if (response.valid) { if (response.valid) {
next(); next()
} else { } 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) { } catch (err) {
logger.error( logger.error(
!req.headers.authorization !req.headers.authorization
? "Please add an Authorization header" ? 'Please add an Authorization header'
: err : 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) => { app.use((req, res, next) => {
if (sensitiveRoutes[req.method][req.path]) { if (sensitiveRoutes[req.method][req.path]) {
@ -84,7 +125,7 @@ const server = program => {
path: req.path, path: req.path,
sessionId: req.sessionId sessionId: req.sessionId
}) })
); )
} else { } else {
console.log( console.log(
JSON.stringify({ JSON.stringify({
@ -96,10 +137,10 @@ const server = program => {
query: req.query, query: req.query,
sessionId: req.sessionId sessionId: req.sessionId
}) })
); )
} }
next(); next()
}); })
app.use( app.use(
session({ session({
secret: defaults.sessionSecret, secret: defaults.sessionSecret,
@ -108,25 +149,23 @@ const server = program => {
rolling: true, rolling: true,
saveUninitialized: true saveUninitialized: true
}) })
); )
app.use(bodyParser.urlencoded({ extended: "true" })); app.use(bodyParser.urlencoded({ extended: 'true' }))
app.use(bodyParser.json()); app.use(bodyParser.json())
app.use(bodyParser.json({ type: "application/vnd.api+json" })); app.use(bodyParser.json({ type: 'application/vnd.api+json' }))
app.use(methodOverride()); app.use(methodOverride())
// WARNING // WARNING
// error handler middleware, KEEP 4 parameters as express detects the // error handler middleware, KEEP 4 parameters as express detects the
// arity of the function to treat it as a err handling middleware // arity of the function to treat it as a err handling middleware
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
app.use((err, _, res, __) => { app.use((err, _, res, __) => {
// Do logging and user-friendly error message display // Do logging and user-friendly error message display
logger.error(err); logger.error(err)
res res.status(500).send({ status: 500, errorMessage: 'internal error' })
.status(500) })
.send({ status: 500, errorMessage: "internal error" });
});
const CA = LightningServices.servicesConfig.lndCertPath const CA = LightningServices.servicesConfig.lndCertPath
const CA_KEY = CA.replace("cert", "key") const CA_KEY = CA.replace('cert', 'key')
const createServer = async () => { const createServer = async () => {
try { try {
@ -134,62 +173,60 @@ const server = program => {
const [key, cert] = await Promise.all([ const [key, cert] = await Promise.all([
FS.readFile(CA_KEY), FS.readFile(CA_KEY),
FS.readFile(CA) 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); const httpServer = Http.Server(app)
return httpServer; return httpServer
} catch (err) { } catch (err) {
logger.error(err.message); logger.error(err.message)
logger.error("An error has occurred while finding an LND cert to use to open an HTTPS server") logger.error(
logger.warn("Falling back to opening an HTTP server...") 'An error has occurred while finding an LND cert to use to open an HTTPS server'
const httpServer = Http.Server(app); )
logger.warn('Falling back to opening an HTTP server...')
const httpServer = Http.Server(app)
return httpServer 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, io,
lnd, lnd,
program.user, program.user,
program.pwd, program.pwd,
program.limituser, program.limituser,
program.limitpwd program.limitpwd
); )
require("./routes")( require('./routes')(app, defaults, Sockets, {
app,
defaults,
Sockets,
{
serverHost: module.serverHost, serverHost: module.serverHost,
serverPort: module.serverPort, serverPort: module.serverPort,
usetls: program.usetls, usetls: program.usetls,
CA, CA,
CA_KEY CA_KEY
} })
);
// enable CORS headers // enable CORS headers
app.use(require("./cors")); app.use(require('./cors'))
// app.use(bodyParser.json({limit: '100000mb'})); // app.use(bodyParser.json({limit: '100000mb'}));
app.use(bodyParser.json({ limit: "50mb" })); app.use(bodyParser.json({ limit: '50mb' }))
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true })); 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( 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'); // const localtunnel = require('localtunnel');
// //
@ -198,15 +235,15 @@ const server = program => {
// console.log('t', t.url); // console.log('t', t.url);
// }); // });
} catch (err) { } catch (err) {
logger.info(err); logger.info(err)
logger.info("Restarting server in 30 seconds..."); logger.info('Restarting server in 30 seconds...')
await wait(30); await wait(30)
startServer(); startServer()
return false; return false
}
} }
};
startServer(); startServer()
}; }
module.exports = server; module.exports = server

View file

@ -6,52 +6,130 @@ const { Buffer } = require('buffer')
const APIKeyPair = new Map() const APIKeyPair = new Map()
const authorizedDevices = new Map() const authorizedDevices = new Map()
module.exports = { const Encryption = {
encrypt: ({ deviceId, message }) => { encryptKey: ({ deviceId, message }) => {
if (!authorizedDevices.has(deviceId)) { if (!authorizedDevices.has(deviceId)) {
throw { field: 'deviceId', message: 'Unknown Device ID' } throw { field: 'deviceId', message: 'Unknown Device ID' }
} }
const devicePublicKey = authorizedDevices.get(deviceId) const devicePublicKey = authorizedDevices.get(deviceId)
const data = Buffer.from(message) 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') return encryptedData.toString('base64')
}, },
decrypt: ({ deviceId, message }) => { decryptKey: ({ deviceId, message }) => {
if (!authorizedDevices.has(deviceId)) { if (!authorizedDevices.has(deviceId)) {
throw { field: 'deviceId', message: 'Unknown Device ID' } throw { field: 'deviceId', message: 'Unknown Device ID' }
} }
const data = Buffer.from(message, 'base64') const data = Buffer.from(message, 'base64')
const encryptedData = Crypto.privateDecrypt(APIKeyPair.private, data) const encryptedData = Crypto.privateDecrypt(
return encryptedData.toString('base64') {
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 }) => authorizeDevice: ({ deviceId, publicKey }) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
if (authorizedDevices.has(deviceId)) { if (authorizedDevices.has(deviceId)) {
const error = { success: false, message: 'Device already exists' } const devicePublicKey = APIKeyPair.get(deviceId).publicKey
reject(error) const deviceExists = {
return error success: true,
APIPublicKey: devicePublicKey
}
resolve(deviceExists)
return deviceExists
} }
authorizedDevices.set(deviceId, publicKey) authorizedDevices.set(deviceId, publicKey)
Crypto.generateKeyPair( Crypto.generateKeyPair(
'rsa', 'rsa',
{ {
modulusLength: 4096 modulusLength: 4096,
privateKeyEncoding: {
type: 'pkcs1',
format: 'pem'
},
publicKeyEncoding: {
type: 'pkcs1',
format: 'pem'
}
}, },
(err, publicKey, privateKey) => { (err, publicKey, privateKey) => {
if (err) { if (err) {
reject({ field: 'APIKeyPair', errorMessage: err }) console.error(err)
reject(err)
return err return err
} }
APIKeyPair.set(deviceId, { const exportedKey = {
publicKey, publicKey,
privateKey privateKey
}
APIKeyPair.set(deviceId, exportedKey)
resolve({
success: true,
APIPublicKey: exportedKey.publicKey
}) })
resolve({ success: true })
} }
) )
}), }),
@ -59,3 +137,5 @@ module.exports = {
authorizedDevices.delete(deviceId) authorizedDevices.delete(deviceId)
} }
} }
module.exports = Encryption

View file

@ -13,7 +13,8 @@ module.exports = {
"/api/lnd/connect": true, "/api/lnd/connect": true,
"/api/lnd/wallet": true, "/api/lnd/wallet": true,
"/api/lnd/wallet/existing": true, "/api/lnd/wallet/existing": true,
"/api/lnd/auth": true "/api/lnd/auth": true,
"/api/security/exchangeKeys": true
}, },
PUT: {}, PUT: {},
DELETE: {} DELETE: {}
@ -26,5 +27,6 @@ module.exports = {
}, },
PUT: {}, PUT: {},
DELETE: {} DELETE: {}
} },
nonEncryptedRoutes: ['/api/security/exchangeKeys', '/healthz', '/ping', '/api/lnd/wallet/status']
} }

View file

@ -873,7 +873,7 @@ ascli@~1:
colour "~0.7.1" colour "~0.7.1"
optjs "~3.2.2" optjs "~3.2.2"
asn1@~0.2.3: asn1@^0.2.4, asn1@~0.2.3:
version "0.2.4" version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
@ -4341,6 +4341,13 @@ node-pre-gyp@^0.13.0:
semver "^5.3.0" semver "^5.3.0"
tar "^4" 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: nodemon@^1.19.3:
version "1.19.3" version "1.19.3"
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.19.3.tgz#db71b3e62aef2a8e1283a9fa00164237356102c0" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.19.3.tgz#db71b3e62aef2a8e1283a9fa00164237356102c0"