Deprecate RSA Encryption
This commit is contained in:
parent
485ec10465
commit
7b1e7ae97a
3 changed files with 11 additions and 350 deletions
126
src/routes.js
126
src/routes.js
|
|
@ -20,7 +20,6 @@ const path = require('path')
|
||||||
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 ECC = require('../utils/ECC')
|
const ECC = require('../utils/ECC')
|
||||||
const LightningServices = require('../utils/lightningServices')
|
const LightningServices = require('../utils/lightningServices')
|
||||||
const lndErrorManager = require('../utils/lightningServices/errors')
|
const lndErrorManager = require('../utils/lightningServices/errors')
|
||||||
|
|
@ -214,98 +213,13 @@ module.exports = async (
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
app.use((req, res, next) => {
|
|
||||||
const legacyDeviceId = req.headers['x-shockwallet-device-id']
|
|
||||||
const deviceId = req.headers['encryption-device-id']
|
|
||||||
try {
|
|
||||||
if (
|
|
||||||
nonEncryptedRoutes.includes(req.path) ||
|
|
||||||
process.env.DISABLE_SHOCK_ENCRYPTION === 'true' ||
|
|
||||||
(deviceId && !legacyDeviceId)
|
|
||||||
) {
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!legacyDeviceId) {
|
|
||||||
const error = {
|
|
||||||
field: 'deviceId',
|
|
||||||
message: 'Please specify a device ID'
|
|
||||||
}
|
|
||||||
logger.error('Please specify a device ID')
|
|
||||||
return res.status(401).json(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Encryption.isAuthorizedDevice({ deviceId: legacyDeviceId })) {
|
|
||||||
const error = {
|
|
||||||
field: 'deviceId',
|
|
||||||
message: 'Please specify a device ID'
|
|
||||||
}
|
|
||||||
logger.error('Unknown Device')
|
|
||||||
return res.status(401).json(error)
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!req.body.encryptionKey &&
|
|
||||||
!req.body.iv &&
|
|
||||||
!req.headers['x-shock-encryption-token']
|
|
||||||
) {
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
let reqData = null
|
|
||||||
let IV = null
|
|
||||||
let encryptedKey = null
|
|
||||||
let encryptedToken = null
|
|
||||||
if (req.method === 'GET' || req.method === 'DELETE') {
|
|
||||||
if (req.headers['x-shock-encryption-token']) {
|
|
||||||
encryptedToken = req.headers['x-shock-encryption-token']
|
|
||||||
encryptedKey = req.headers['x-shock-encryption-key']
|
|
||||||
IV = req.headers['x-shock-encryption-iv']
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
encryptedToken = req.body.token
|
|
||||||
encryptedKey = req.body.encryptionKey || req.body.encryptedKey
|
|
||||||
IV = req.body.iv
|
|
||||||
reqData = req.body.data || req.body.encryptedData
|
|
||||||
}
|
|
||||||
const decryptedKey = Encryption.decryptKey({
|
|
||||||
deviceId: legacyDeviceId,
|
|
||||||
message: encryptedKey
|
|
||||||
})
|
|
||||||
if (reqData) {
|
|
||||||
const decryptedMessage = Encryption.decryptMessage({
|
|
||||||
message: reqData,
|
|
||||||
key: decryptedKey,
|
|
||||||
iv: IV
|
|
||||||
})
|
|
||||||
req.body = JSON.parse(decryptedMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
const decryptedToken = encryptedToken
|
|
||||||
? Encryption.decryptMessage({
|
|
||||||
message: encryptedToken,
|
|
||||||
key: decryptedKey,
|
|
||||||
iv: IV
|
|
||||||
})
|
|
||||||
: null
|
|
||||||
|
|
||||||
if (decryptedToken) {
|
|
||||||
req.headers.authorization = decryptedToken
|
|
||||||
}
|
|
||||||
|
|
||||||
return next()
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(err)
|
|
||||||
return res.status(401).json(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.use(async (req, res, next) => {
|
app.use(async (req, res, next) => {
|
||||||
const legacyDeviceId = req.headers['x-shockwallet-device-id']
|
|
||||||
const deviceId = req.headers['encryption-device-id']
|
const deviceId = req.headers['encryption-device-id']
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
nonEncryptedRoutes.includes(req.path) ||
|
nonEncryptedRoutes.includes(req.path) ||
|
||||||
process.env.DISABLE_SHOCK_ENCRYPTION === 'true' ||
|
process.env.DISABLE_SHOCK_ENCRYPTION === 'true' ||
|
||||||
(legacyDeviceId && !deviceId)
|
!deviceId
|
||||||
) {
|
) {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
@ -499,44 +413,6 @@ module.exports = async (
|
||||||
res.json({ msg: 'OK' })
|
res.json({ msg: 'OK' })
|
||||||
})
|
})
|
||||||
|
|
||||||
app.post('/api/security/exchangeKeys', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { publicKey, deviceId } = req.body
|
|
||||||
|
|
||||||
if (!publicKey) {
|
|
||||||
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
|
|
||||||
})
|
|
||||||
logger.info(authorizedDevice)
|
|
||||||
return res.json(authorizedDevice)
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(err)
|
|
||||||
return res.status(401).json({
|
|
||||||
field: 'unknown',
|
|
||||||
message: err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post('/api/encryption/exchange', async (req, res) => {
|
app.post('/api/encryption/exchange', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { publicKey, deviceId } = req.body
|
const { publicKey, deviceId } = req.body
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
const { generateRandomString } = require('../utils/encryptionStore')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @prettier
|
* @prettier
|
||||||
*/
|
*/
|
||||||
|
|
@ -22,7 +20,6 @@ const server = program => {
|
||||||
|
|
||||||
const ECC = require('../utils/ECC')
|
const ECC = require('../utils/ECC')
|
||||||
const LightningServices = require('../utils/lightningServices')
|
const LightningServices = require('../utils/lightningServices')
|
||||||
const Encryption = require('../utils/encryptionStore')
|
|
||||||
const app = Express()
|
const app = Express()
|
||||||
|
|
||||||
const compression = require('compression')
|
const compression = require('compression')
|
||||||
|
|
@ -124,7 +121,6 @@ const server = program => {
|
||||||
* @param {(() => void)} next
|
* @param {(() => void)} next
|
||||||
*/
|
*/
|
||||||
const modifyResponseBody = (req, res, next) => {
|
const modifyResponseBody = (req, res, next) => {
|
||||||
const legacyDeviceId = req.headers['x-shockwallet-device-id']
|
|
||||||
const deviceId = req.headers['encryption-device-id']
|
const deviceId = req.headers['encryption-device-id']
|
||||||
const oldSend = res.send
|
const oldSend = res.send
|
||||||
|
|
||||||
|
|
@ -136,38 +132,6 @@ const server = program => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (legacyDeviceId) {
|
|
||||||
res.send = (...args) => {
|
|
||||||
if (args[0] && args[0].encryptedData && args[0].encryptionKey) {
|
|
||||||
logger.warn('Response loop detected!')
|
|
||||||
oldSend.apply(res, args)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const { cached, hash } = cacheCheck({ req, res, args, send: oldSend })
|
|
||||||
|
|
||||||
if (cached) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// arguments[0] (or `data`) contains the response body
|
|
||||||
const authorized = Encryption.isAuthorizedDevice({
|
|
||||||
deviceId: legacyDeviceId
|
|
||||||
})
|
|
||||||
const encryptedMessage = authorized
|
|
||||||
? Encryption.encryptMessage({
|
|
||||||
message: args[0] ? args[0] : {},
|
|
||||||
deviceId: legacyDeviceId,
|
|
||||||
metadata: {
|
|
||||||
hash
|
|
||||||
}
|
|
||||||
})
|
|
||||||
: args[0]
|
|
||||||
args[0] = JSON.stringify(encryptedMessage)
|
|
||||||
oldSend.apply(res, args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deviceId) {
|
if (deviceId) {
|
||||||
res.send = (...args) => {
|
res.send = (...args) => {
|
||||||
if (args[0] && args[0].ciphertext && args[0].iv) {
|
if (args[0] && args[0].ciphertext && args[0].iv) {
|
||||||
|
|
@ -288,7 +252,7 @@ const server = program => {
|
||||||
return randomField
|
return randomField
|
||||||
}
|
}
|
||||||
|
|
||||||
const newValue = await Encryption.generateRandomString(length)
|
const newValue = await ECC.generateRandomString(length)
|
||||||
await Storage.setItem(fieldName, newValue)
|
await Storage.setItem(fieldName, newValue)
|
||||||
return newValue
|
return newValue
|
||||||
}
|
}
|
||||||
|
|
@ -440,14 +404,16 @@ const server = program => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if(process.env.ALLOW_UNLOCKED_LND === 'true'){
|
if (process.env.ALLOW_UNLOCKED_LND === 'true') {
|
||||||
const codes = await Storage.valuesWithKeyMatch(/^UnlockedAccessSecrets\//u)
|
const codes = await Storage.valuesWithKeyMatch(
|
||||||
if(codes.length === 0){
|
/^UnlockedAccessSecrets\//u
|
||||||
const code = generateRandomString(12)
|
)
|
||||||
|
if (codes.length === 0) {
|
||||||
|
const code = ECC.generateRandomString(12)
|
||||||
await Storage.setItem(`UnlockedAccessSecrets/${code}`, false)
|
await Storage.setItem(`UnlockedAccessSecrets/${code}`, false)
|
||||||
logger.info("the access code is:"+code)
|
logger.info('the access code is:' + code)
|
||||||
} else if(codes.length === 1 || codes[0] === false){
|
} else if (codes.length === 1 || codes[0] === false) {
|
||||||
logger.info("the access code is:"+codes[0])
|
logger.info('the access code is:' + codes[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
serverInstance.listen(serverPort, serverHost)
|
serverInstance.listen(serverPort, serverHost)
|
||||||
|
|
|
||||||
|
|
@ -1,181 +0,0 @@
|
||||||
/**
|
|
||||||
* @prettier
|
|
||||||
*/
|
|
||||||
const Crypto = require('crypto')
|
|
||||||
const { Buffer } = require('buffer')
|
|
||||||
const logger = require('../config/log')
|
|
||||||
|
|
||||||
const APIKeyPair = new Map()
|
|
||||||
const authorizedDevices = new Map()
|
|
||||||
|
|
||||||
const nonEncryptedEvents = [
|
|
||||||
'ping',
|
|
||||||
'disconnect',
|
|
||||||
'IS_GUN_AUTH',
|
|
||||||
'SET_LAST_SEEN_APP'
|
|
||||||
]
|
|
||||||
|
|
||||||
const Encryption = {
|
|
||||||
/**
|
|
||||||
* @param {string} event
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isNonEncrypted: event => nonEncryptedEvents.includes(event),
|
|
||||||
/**
|
|
||||||
* @param {{ deviceId: string , message: string }} arg0
|
|
||||||
*/
|
|
||||||
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')
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* @param {{ deviceId: string , message: string }} arg0
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
return encryptedData.toString()
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* @param {{ deviceId: string , message: any , metadata?: any}} arg0
|
|
||||||
*/
|
|
||||||
encryptMessage: ({ deviceId, message, metadata = {} }) => {
|
|
||||||
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')
|
|
||||||
const encryptedMessage = {
|
|
||||||
encryptedData,
|
|
||||||
encryptedKey,
|
|
||||||
iv: iv.toString('hex'),
|
|
||||||
metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
return encryptedMessage
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* @param {{ message: string , key: string , iv: string }} arg0
|
|
||||||
*/
|
|
||||||
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()
|
|
||||||
|
|
||||||
return decryptedData.toString()
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* @param {{ deviceId: string }} arg0
|
|
||||||
*/
|
|
||||||
isAuthorizedDevice: ({ deviceId }) => {
|
|
||||||
if (authorizedDevices.has(deviceId)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* @param {{ deviceId: string , publicKey: string }} arg0
|
|
||||||
*/
|
|
||||||
authorizeDevice: ({ deviceId, publicKey }) =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
authorizedDevices.set(deviceId, publicKey)
|
|
||||||
Crypto.generateKeyPair(
|
|
||||||
'rsa',
|
|
||||||
{
|
|
||||||
modulusLength: 2048,
|
|
||||||
privateKeyEncoding: {
|
|
||||||
type: 'pkcs1',
|
|
||||||
format: 'pem'
|
|
||||||
},
|
|
||||||
publicKeyEncoding: {
|
|
||||||
type: 'pkcs1',
|
|
||||||
format: 'pem'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(err, publicKey, privateKey) => {
|
|
||||||
if (err) {
|
|
||||||
// @ts-ignore
|
|
||||||
logger.error(err)
|
|
||||||
reject(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const exportedKey = {
|
|
||||||
publicKey,
|
|
||||||
privateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
APIKeyPair.set(deviceId, exportedKey)
|
|
||||||
resolve({
|
|
||||||
success: true,
|
|
||||||
APIPublicKey: exportedKey.publicKey
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
/**
|
|
||||||
* @param {{ deviceId: string }} arg0
|
|
||||||
*/
|
|
||||||
unAuthorizeDevice: ({ deviceId }) => {
|
|
||||||
authorizedDevices.delete(deviceId)
|
|
||||||
},
|
|
||||||
generateRandomString: (length = 16) =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
Crypto.randomBytes(length, (err, buffer) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = buffer.toString('hex')
|
|
||||||
resolve(token)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Encryption
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue