Merge pull request #22 from shocknet/feature/end-to-end-encryption

Feature/end to end encryption
This commit is contained in:
Daniel Lugo 2020-01-30 16:42:05 -04:00 committed by GitHub
commit 2b043f5943
7 changed files with 551 additions and 192 deletions

View file

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

View file

@ -4,6 +4,7 @@
const Gun = require('gun')
const debounce = require('lodash/debounce')
const once = require('lodash/once')
const Encryption = require('../../../utils/encryptionStore')
/** @type {import('../contact-api/SimpleGUN').ISEA} */
// @ts-ignore
@ -96,6 +97,7 @@ const Action = require('../action-constants.js')
const API = require('../contact-api/index')
const Config = require('../config')
const Event = require('../event-constants')
// const { nonEncryptedRoutes } = require('../../../utils/protectedRoutes')
/**
* @typedef {import('../contact-api/SimpleGUN').GUNNode} GUNNode
@ -281,37 +283,116 @@ class Mediator {
* @param {Readonly<SimpleSocket>} socket
*/
constructor(socket) {
this.socket = socket
this.socket = this.encryptSocketInstance(socket)
this.connected = true
socket.on('disconnect', this.onDisconnect)
this.socket.on('disconnect', this.onDisconnect)
socket.on(Action.ACCEPT_REQUEST, this.acceptRequest)
socket.on(Action.BLACKLIST, this.blacklist)
socket.on(Action.GENERATE_NEW_HANDSHAKE_NODE, this.generateHandshakeNode)
socket.on(Action.SEND_HANDSHAKE_REQUEST, this.sendHandshakeRequest)
socket.on(
this.socket.on(Action.ACCEPT_REQUEST, this.acceptRequest)
this.socket.on(Action.BLACKLIST, this.blacklist)
this.socket.on(
Action.GENERATE_NEW_HANDSHAKE_NODE,
this.generateHandshakeNode
)
this.socket.on(Action.SEND_HANDSHAKE_REQUEST, this.sendHandshakeRequest)
this.socket.on(
Action.SEND_HANDSHAKE_REQUEST_WITH_INITIAL_MSG,
this.sendHRWithInitialMsg
)
socket.on(Action.SEND_MESSAGE, this.sendMessage)
socket.on(Action.SEND_PAYMENT, this.sendPayment)
socket.on(Action.SET_AVATAR, this.setAvatar)
socket.on(Action.SET_DISPLAY_NAME, this.setDisplayName)
socket.on(Action.SET_BIO, this.setBio)
this.socket.on(Action.SEND_MESSAGE, this.sendMessage)
this.socket.on(Action.SET_AVATAR, this.setAvatar)
this.socket.on(Action.SET_DISPLAY_NAME, this.setDisplayName)
this.socket.on(Action.SEND_PAYMENT, this.sendPayment)
this.socket.on(Action.SET_BIO, this.setBio)
socket.on(Event.ON_AVATAR, this.onAvatar)
socket.on(Event.ON_BLACKLIST, this.onBlacklist)
socket.on(Event.ON_CHATS, this.onChats)
socket.on(Event.ON_DISPLAY_NAME, this.onDisplayName)
socket.on(Event.ON_HANDSHAKE_ADDRESS, this.onHandshakeAddress)
socket.on(Event.ON_RECEIVED_REQUESTS, this.onReceivedRequests)
socket.on(Event.ON_SENT_REQUESTS, this.onSentRequests)
socket.on(Event.ON_BIO, this.onBio)
socket.on(Event.ON_SEED_BACKUP, this.onSeedBackup)
this.socket.on(Event.ON_AVATAR, this.onAvatar)
this.socket.on(Event.ON_BLACKLIST, this.onBlacklist)
this.socket.on(Event.ON_CHATS, this.onChats)
this.socket.on(Event.ON_DISPLAY_NAME, this.onDisplayName)
this.socket.on(Event.ON_HANDSHAKE_ADDRESS, this.onHandshakeAddress)
this.socket.on(Event.ON_RECEIVED_REQUESTS, this.onReceivedRequests)
this.socket.on(Event.ON_SENT_REQUESTS, this.onSentRequests)
this.socket.on(Event.ON_BIO, this.onBio)
this.socket.on(Event.ON_SEED_BACKUP, this.onSeedBackup)
socket.on(IS_GUN_AUTH, this.isGunAuth)
this.socket.on(IS_GUN_AUTH, this.isGunAuth)
}
encryptSocketInstance = socket => {
return {
on: (eventName, cb) => {
const deviceId = socket.handshake.query['x-shockwallet-device-id']
socket.on(eventName, data => {
try {
// if (nonEncryptedEvents.includes(eventName)) {
// return cb(data)
// }
if (!data) {
return cb(data)
}
if (!deviceId) {
const error = {
field: 'deviceId',
message: 'Please specify a device ID'
}
console.error(error)
return false
}
if (!Encryption.isAuthorizedDevice({ deviceId })) {
const error = {
field: 'deviceId',
message: 'Please specify a device ID'
}
console.error('Unknown Device', error)
return false
}
if(typeof data === 'string'){
data = JSON.parse(data)
}
console.log('Event:', eventName)
console.log('Data:', data)
console.log('Decrypt params:', {
deviceId,
message: data.encryptedKey
})
const decryptedKey = Encryption.decryptKey({
deviceId,
message: data.encryptedKey
})
const decryptedMessage = Encryption.decryptMessage({
message: data.encryptedData,
key: decryptedKey,
iv: data.iv
})
const decryptedData = JSON.parse(decryptedMessage)
return cb(decryptedData)
} catch (err) {
console.error(err)
return false
}
})
},
emit: (eventName, data) => {
try {
const deviceId = socket.handshake.query['x-shockwallet-device-id']
const authorized = Encryption.isAuthorizedDevice({ deviceId })
const encryptedMessage = authorized
? Encryption.encryptMessage({
message: data,
deviceId
})
: data
console.log('Sending Message...', eventName, data, encryptedMessage)
socket.emit(eventName, encryptedMessage)
} catch (err) {
console.error(err)
}
}
}
}
isGunAuth = () => {
@ -725,6 +806,8 @@ class Mediator {
try {
const { token } = body
console.log('ON_CHATS', body)
await throwOnInvalidToken(token)
API.Events.onChats(

View file

@ -5,27 +5,37 @@
*/
"use strict";
const Http = require("axios");
const Axios = require("axios");
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, nonEncryptedRoutes } = require("../utils/protectedRoutes");
const GunActions = require("../services/gunDB/contact-api/actions")
const { unprotectedRoutes } = require("../utils/protectedRoutes");
const DEFAULT_MAX_NUM_ROUTES_TO_QUERY = 10;
const SESSION_ID = uuid();
// module.exports = (app) => {
module.exports = (
module.exports = async (
app,
config,
mySocketsEvents,
{ serverPort }
{ serverPort, CA, CA_KEY, usetls }
) => {
const Http = Axios.create({
httpsAgent: new httpsAgent.Agent({
ca: await FS.readFile(CA)
})
})
const sanitizeLNDError = (message = "") =>
message.toLowerCase().includes("unknown")
? message
@ -82,7 +92,7 @@ module.exports = (
const serviceStatus = await getAvailableService();
const LNDStatus = serviceStatus;
try {
const APIHealth = await Http.get(`http://localhost:${serverPort}/ping`);
const APIHealth = await Http.get(`${usetls ? 'https' : 'http'}://localhost:${serverPort}/ping`);
const APIStatus = {
message: APIHealth.data,
responseTime: APIHealth.headers["x-response-time"],
@ -93,6 +103,7 @@ module.exports = (
APIStatus
};
} catch (err) {
console.error(err);
const APIStatus = {
message: err.response.data,
responseTime: err.response.headers["x-response-time"],
@ -109,7 +120,7 @@ module.exports = (
const health = await checkHealth();
if (health.LNDStatus.success) {
if (err) {
res.send({
res.json({
errorMessage: sanitizeLNDError(err.message)
});
} else {
@ -117,7 +128,7 @@ module.exports = (
}
} else {
res.status(500);
res.send({ errorMessage: "LND is down" });
res.json({ errorMessage: "LND is down" });
}
};
@ -185,6 +196,58 @@ module.exports = (
}
};
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);
}
if (req.method === "GET") {
console.log("Method:", req.method);
return next();
}
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)
@ -240,7 +303,7 @@ module.exports = (
*/
app.get("/health", async (req, res) => {
const health = await checkHealth();
res.send(health);
res.json(health);
});
/**
@ -248,11 +311,11 @@ module.exports = (
*/
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) => {
@ -260,6 +323,38 @@ module.exports = (
res.json({ msg: "OK" });
});
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) => {
try {
const walletStatus = await walletExists();
@ -281,6 +376,7 @@ module.exports = (
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
@ -326,7 +422,7 @@ module.exports = (
}
res.status(500);
res.send({
res.json({
field: "health",
errorMessage: sanitizeLNDError(health.LNDStatus.message),
success: false
@ -335,7 +431,7 @@ module.exports = (
} 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;
}
});
@ -357,10 +453,10 @@ module.exports = (
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();
@ -424,12 +520,12 @@ module.exports = (
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);
@ -608,13 +704,13 @@ module.exports = (
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);
@ -639,13 +735,13 @@ module.exports = (
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);
@ -661,13 +757,13 @@ module.exports = (
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);
@ -683,13 +779,13 @@ module.exports = (
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);
@ -705,13 +801,13 @@ module.exports = (
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);
@ -726,13 +822,13 @@ module.exports = (
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 },
@ -742,7 +838,7 @@ module.exports = (
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);
@ -757,20 +853,20 @@ module.exports = (
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);
@ -786,13 +882,13 @@ module.exports = (
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);
@ -808,13 +904,13 @@ module.exports = (
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);
@ -901,10 +997,10 @@ module.exports = (
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);
@ -927,13 +1023,13 @@ module.exports = (
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);
@ -949,13 +1045,13 @@ module.exports = (
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);
@ -971,13 +1067,13 @@ module.exports = (
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;
}
@ -986,13 +1082,13 @@ module.exports = (
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;
}
@ -1015,11 +1111,11 @@ module.exports = (
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);
@ -1037,13 +1133,13 @@ module.exports = (
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);
@ -1060,7 +1156,7 @@ module.exports = (
res.sendStatus(403);
} else {
res.status(500);
res.send({ errorMessage: "LND is down" });
res.json({ errorMessage: "LND is down" });
}
return;
}
@ -1084,10 +1180,10 @@ module.exports = (
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)
@ -1103,7 +1199,7 @@ module.exports = (
res.sendStatus(403);
} else {
res.status(500);
res.send({ errorMessage: "LND is down" });
res.json({ errorMessage: "LND is down" });
}
}
const { channelPoint, outputIndex } = req.body;
@ -1131,13 +1227,13 @@ module.exports = (
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" });
}
}
});
@ -1152,7 +1248,7 @@ module.exports = (
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 };
@ -1184,12 +1280,12 @@ module.exports = (
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" });
}
});
@ -1205,7 +1301,7 @@ module.exports = (
res.sendStatus(403);
} else {
res.status(500);
res.send({ errorMessage: "LND is down" });
res.json({ errorMessage: "LND is down" });
}
return false;
}
@ -1221,13 +1317,13 @@ module.exports = (
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;
}
@ -1245,7 +1341,7 @@ module.exports = (
res.sendStatus(403);
} else {
res.status(500);
res.send({ errorMessage: "LND is down" });
res.json({ errorMessage: "LND is down" });
}
}
lightning.signMessage(
@ -1255,10 +1351,10 @@ module.exports = (
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);
@ -1277,10 +1373,10 @@ module.exports = (
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);
@ -1298,7 +1394,7 @@ module.exports = (
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 };
@ -1308,13 +1404,13 @@ module.exports = (
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);
@ -1334,10 +1430,10 @@ module.exports = (
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);
@ -1360,12 +1456,12 @@ module.exports = (
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);

View file

@ -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,79 +149,84 @@ 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 createServer = async () => {
try {
if (program.usetls) {
if (LightningServices.servicesConfig.lndCertPath && program.usetls) {
const [key, cert] = await Promise.all([
FS.readFile(program.usetls + "/key.pem"),
FS.readFile(program.usetls + "/cert.pem")
]);
const httpsServer = Https.createServer({ key, cert }, app);
FS.readFile(CA_KEY),
FS.readFile(CA)
])
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);
throw 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)
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
}
);
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');
//
@ -189,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

125
utils/encryptionStore.js Normal file
View file

@ -0,0 +1,125 @@
/**
* @prettier
*/
const Crypto = require('crypto')
const { Buffer } = require('buffer')
const APIKeyPair = new Map()
const authorizedDevices = new Map()
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(
{
key: devicePublicKey,
padding: Crypto.constants.RSA_PKCS1_PADDING
},
data
)
return encryptedData.toString('base64')
},
decryptKey: ({ deviceId, message }) => {
if (!authorizedDevices.has(deviceId)) {
throw { field: 'deviceId', message: 'Unknown Device ID' }
}
const data = Buffer.from(message, '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 }) => {
if (authorizedDevices.has(deviceId)) {
return true
}
return false
},
authorizeDevice: ({ deviceId, publicKey }) =>
new Promise((resolve, reject) => {
authorizedDevices.set(deviceId, publicKey)
Crypto.generateKeyPair(
'rsa',
{
modulusLength: 4096,
privateKeyEncoding: {
type: 'pkcs1',
format: 'pem'
},
publicKeyEncoding: {
type: 'pkcs1',
format: 'pem'
}
},
(err, publicKey, privateKey) => {
if (err) {
console.error(err)
reject(err)
return err
}
const exportedKey = {
publicKey,
privateKey
}
APIKeyPair.set(deviceId, exportedKey)
resolve({
success: true,
APIPublicKey: exportedKey.publicKey
})
}
)
}),
unAuthorizeDevice: ({ deviceId }) => {
authorizedDevices.delete(deviceId)
}
}
module.exports = Encryption

View file

@ -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']
}

View file

@ -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"