Change to revealing module pattern
This commit is contained in:
parent
2fc159f8d8
commit
e86f5a5ad8
2 changed files with 195 additions and 194 deletions
194
utils/ECC/ECC.js
Normal file
194
utils/ECC/ECC.js
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
/** @format */
|
||||||
|
const ECCrypto = require('eccrypto')
|
||||||
|
const Storage = require('node-persist')
|
||||||
|
const FieldError = require('../fieldError')
|
||||||
|
const logger = require('../../config/log')
|
||||||
|
const {
|
||||||
|
generateRandomString,
|
||||||
|
convertBufferToBase64,
|
||||||
|
processKey,
|
||||||
|
convertToEncryptedMessageResponse,
|
||||||
|
convertUTF8ToBuffer,
|
||||||
|
convertToEncryptedMessage,
|
||||||
|
convertBase64ToBuffer
|
||||||
|
} = require('./crypto')
|
||||||
|
|
||||||
|
const nodeKeyPairs = new Map()
|
||||||
|
const devicePublicKeys = new Map()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} EncryptedMessage
|
||||||
|
* @prop {string} ciphertext
|
||||||
|
* @prop {string} iv
|
||||||
|
* @prop {string} mac
|
||||||
|
* @prop {string} ephemPublicKey
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the message supplied is encrypted or not
|
||||||
|
* @param {EncryptedMessage} message
|
||||||
|
*/
|
||||||
|
const isEncryptedMessage = message =>
|
||||||
|
message &&
|
||||||
|
message.ciphertext &&
|
||||||
|
message.iv &&
|
||||||
|
message.mac &&
|
||||||
|
message.ephemPublicKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new encryption key pair that will be used
|
||||||
|
* when communicating with the deviceId specified
|
||||||
|
* @param {string} deviceId
|
||||||
|
*/
|
||||||
|
const generateKeyPair = deviceId => {
|
||||||
|
try {
|
||||||
|
const existingKey = nodeKeyPairs.get(deviceId)
|
||||||
|
|
||||||
|
if (existingKey) {
|
||||||
|
logger.warn('Device ID is already trusted')
|
||||||
|
return {
|
||||||
|
...existingKey,
|
||||||
|
publicKeyBase64: convertBufferToBase64(existingKey.publicKey),
|
||||||
|
privateKeyBase64: convertBufferToBase64(existingKey.privateKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const privateKey = ECCrypto.generatePrivate()
|
||||||
|
const publicKey = ECCrypto.getPublic(privateKey)
|
||||||
|
const privateKeyBase64 = convertBufferToBase64(privateKey)
|
||||||
|
const publicKeyBase64 = convertBufferToBase64(publicKey)
|
||||||
|
|
||||||
|
if (!Buffer.isBuffer(privateKey) || !Buffer.isBuffer(publicKey)) {
|
||||||
|
throw new Error('Invalid KeyPair Generated')
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeKeyPairs.set(deviceId, {
|
||||||
|
privateKey,
|
||||||
|
publicKey
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
privateKey,
|
||||||
|
publicKey,
|
||||||
|
privateKeyBase64,
|
||||||
|
publicKeyBase64
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(
|
||||||
|
'[ENCRYPTION] An error has occurred while generating a new KeyPair',
|
||||||
|
err
|
||||||
|
)
|
||||||
|
logger.error('Device ID:', deviceId)
|
||||||
|
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the specified device has a keypair generated
|
||||||
|
* @param {{ deviceId: string }} arg0
|
||||||
|
*/
|
||||||
|
const isAuthorizedDevice = ({ deviceId }) => devicePublicKeys.has(deviceId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new keypair for the deviceId specified and
|
||||||
|
* saves its publicKey locally
|
||||||
|
* @param {{ deviceId: string, publicKey: string }} arg0
|
||||||
|
*/
|
||||||
|
const authorizeDevice = async ({ deviceId, publicKey }) => {
|
||||||
|
const hostId = await Storage.get('encryption/hostId')
|
||||||
|
devicePublicKeys.set(deviceId, convertBase64ToBuffer(publicKey))
|
||||||
|
const keyPair = generateKeyPair(deviceId)
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
APIPublicKey: keyPair.publicKeyBase64,
|
||||||
|
hostId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts the specified message using the specified deviceId's
|
||||||
|
* public key
|
||||||
|
* @param {{ deviceId: string, message: string | number | boolean }} arg0
|
||||||
|
* @returns {Promise<import('./crypto').EncryptedMessageResponse>}
|
||||||
|
*/
|
||||||
|
const encryptMessage = async ({ message = '', deviceId }) => {
|
||||||
|
const parsedMessage = message.toString()
|
||||||
|
const publicKey = devicePublicKeys.get(deviceId)
|
||||||
|
|
||||||
|
if (!publicKey) {
|
||||||
|
throw new FieldError({
|
||||||
|
field: 'deviceId',
|
||||||
|
message: 'Unauthorized Device ID detected'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const processedPublicKey = processKey(publicKey)
|
||||||
|
const messageBuffer = convertUTF8ToBuffer(parsedMessage)
|
||||||
|
const encryptedMessage = await ECCrypto.encrypt(
|
||||||
|
processedPublicKey,
|
||||||
|
messageBuffer
|
||||||
|
)
|
||||||
|
const encryptedMessageResponse = {
|
||||||
|
ciphertext: encryptedMessage.ciphertext,
|
||||||
|
iv: encryptedMessage.iv,
|
||||||
|
mac: encryptedMessage.mac,
|
||||||
|
ephemPublicKey: encryptedMessage.ephemPublicKey,
|
||||||
|
metadata: {
|
||||||
|
_deviceId: deviceId,
|
||||||
|
_publicKey: publicKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertToEncryptedMessageResponse(encryptedMessageResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the specified message using the API keypair
|
||||||
|
* associated with the specified deviceId
|
||||||
|
* @param {{ encryptedMessage: import('./crypto').EncryptedMessageResponse, deviceId: string }} arg0
|
||||||
|
*/
|
||||||
|
const decryptMessage = async ({ encryptedMessage, deviceId }) => {
|
||||||
|
const keyPair = nodeKeyPairs.get(deviceId)
|
||||||
|
try {
|
||||||
|
if (!keyPair) {
|
||||||
|
throw new FieldError({
|
||||||
|
field: 'deviceId',
|
||||||
|
message: 'Unauthorized Device ID detected'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const processedPrivateKey = processKey(keyPair.privateKey)
|
||||||
|
const decryptedMessage = await ECCrypto.decrypt(
|
||||||
|
processedPrivateKey,
|
||||||
|
convertToEncryptedMessage(encryptedMessage)
|
||||||
|
)
|
||||||
|
const parsedMessage = decryptedMessage.toString('utf8')
|
||||||
|
|
||||||
|
return parsedMessage
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err)
|
||||||
|
if (err.message?.toLowerCase() === 'bad mac') {
|
||||||
|
logger.error(
|
||||||
|
'Bad Mac!',
|
||||||
|
err,
|
||||||
|
convertToEncryptedMessage(encryptedMessage),
|
||||||
|
!!keyPair
|
||||||
|
)
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
isAuthorizedDevice,
|
||||||
|
isEncryptedMessage,
|
||||||
|
generateKeyPair,
|
||||||
|
encryptMessage,
|
||||||
|
decryptMessage,
|
||||||
|
authorizeDevice,
|
||||||
|
generateRandomString,
|
||||||
|
nodeKeyPairs,
|
||||||
|
devicePublicKeys
|
||||||
|
}
|
||||||
|
|
@ -1,194 +1 @@
|
||||||
/** @format */
|
module.exports = require('./ECC')
|
||||||
const ECCrypto = require('eccrypto')
|
|
||||||
const Storage = require('node-persist')
|
|
||||||
const FieldError = require('../fieldError')
|
|
||||||
const logger = require('../../config/log')
|
|
||||||
const {
|
|
||||||
generateRandomString,
|
|
||||||
convertBufferToBase64,
|
|
||||||
processKey,
|
|
||||||
convertToEncryptedMessageResponse,
|
|
||||||
convertUTF8ToBuffer,
|
|
||||||
convertToEncryptedMessage,
|
|
||||||
convertBase64ToBuffer
|
|
||||||
} = require('./crypto')
|
|
||||||
|
|
||||||
const nodeKeyPairs = new Map()
|
|
||||||
const devicePublicKeys = new Map()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} EncryptedMessage
|
|
||||||
* @prop {string} ciphertext
|
|
||||||
* @prop {string} iv
|
|
||||||
* @prop {string} mac
|
|
||||||
* @prop {string} ephemPublicKey
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the message supplied is encrypted or not
|
|
||||||
* @param {EncryptedMessage} message
|
|
||||||
*/
|
|
||||||
const isEncryptedMessage = message =>
|
|
||||||
message &&
|
|
||||||
message.ciphertext &&
|
|
||||||
message.iv &&
|
|
||||||
message.mac &&
|
|
||||||
message.ephemPublicKey
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a new encryption key pair that will be used
|
|
||||||
* when communicating with the deviceId specified
|
|
||||||
* @param {string} deviceId
|
|
||||||
*/
|
|
||||||
const generateKeyPair = deviceId => {
|
|
||||||
try {
|
|
||||||
const existingKey = nodeKeyPairs.get(deviceId)
|
|
||||||
|
|
||||||
if (existingKey) {
|
|
||||||
logger.warn('Device ID is already trusted')
|
|
||||||
return {
|
|
||||||
...existingKey,
|
|
||||||
publicKeyBase64: convertBufferToBase64(existingKey.publicKey),
|
|
||||||
privateKeyBase64: convertBufferToBase64(existingKey.privateKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const privateKey = ECCrypto.generatePrivate()
|
|
||||||
const publicKey = ECCrypto.getPublic(privateKey)
|
|
||||||
const privateKeyBase64 = convertBufferToBase64(privateKey)
|
|
||||||
const publicKeyBase64 = convertBufferToBase64(publicKey)
|
|
||||||
|
|
||||||
if (!Buffer.isBuffer(privateKey) || !Buffer.isBuffer(publicKey)) {
|
|
||||||
throw new Error('Invalid KeyPair Generated')
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeKeyPairs.set(deviceId, {
|
|
||||||
privateKey,
|
|
||||||
publicKey
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
privateKey,
|
|
||||||
publicKey,
|
|
||||||
privateKeyBase64,
|
|
||||||
publicKeyBase64
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(
|
|
||||||
'[ENCRYPTION] An error has occurred while generating a new KeyPair',
|
|
||||||
err
|
|
||||||
)
|
|
||||||
logger.error('Device ID:', deviceId)
|
|
||||||
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the specified device has a keypair generated
|
|
||||||
* @param {{ deviceId: string }} arg0
|
|
||||||
*/
|
|
||||||
const isAuthorizedDevice = ({ deviceId }) => devicePublicKeys.has(deviceId)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a new keypair for the deviceId specified and
|
|
||||||
* saves its publicKey locally
|
|
||||||
* @param {{ deviceId: string, publicKey: string }} arg0
|
|
||||||
*/
|
|
||||||
const authorizeDevice = async ({ deviceId, publicKey }) => {
|
|
||||||
const hostId = await Storage.get('encryption/hostId')
|
|
||||||
devicePublicKeys.set(deviceId, convertBase64ToBuffer(publicKey))
|
|
||||||
const keyPair = generateKeyPair(deviceId)
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
APIPublicKey: keyPair.publicKeyBase64,
|
|
||||||
hostId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypts the specified message using the specified deviceId's
|
|
||||||
* public key
|
|
||||||
* @param {{ deviceId: string, message: string | number | boolean }} arg0
|
|
||||||
* @returns {Promise<import('./crypto').EncryptedMessageResponse>}
|
|
||||||
*/
|
|
||||||
const encryptMessage = async ({ message = '', deviceId }) => {
|
|
||||||
const parsedMessage = message.toString()
|
|
||||||
const publicKey = devicePublicKeys.get(deviceId)
|
|
||||||
|
|
||||||
if (!publicKey) {
|
|
||||||
throw new FieldError({
|
|
||||||
field: 'deviceId',
|
|
||||||
message: 'Unauthorized Device ID detected'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const processedPublicKey = processKey(publicKey)
|
|
||||||
const messageBuffer = convertUTF8ToBuffer(parsedMessage)
|
|
||||||
const encryptedMessage = await ECCrypto.encrypt(
|
|
||||||
processedPublicKey,
|
|
||||||
messageBuffer
|
|
||||||
)
|
|
||||||
const encryptedMessageResponse = {
|
|
||||||
ciphertext: encryptedMessage.ciphertext,
|
|
||||||
iv: encryptedMessage.iv,
|
|
||||||
mac: encryptedMessage.mac,
|
|
||||||
ephemPublicKey: encryptedMessage.ephemPublicKey,
|
|
||||||
metadata: {
|
|
||||||
_deviceId: deviceId,
|
|
||||||
_publicKey: publicKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return convertToEncryptedMessageResponse(encryptedMessageResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypts the specified message using the API keypair
|
|
||||||
* associated with the specified deviceId
|
|
||||||
* @param {{ encryptedMessage: import('./crypto').EncryptedMessageResponse, deviceId: string }} arg0
|
|
||||||
*/
|
|
||||||
const decryptMessage = async ({ encryptedMessage, deviceId }) => {
|
|
||||||
const keyPair = nodeKeyPairs.get(deviceId)
|
|
||||||
try {
|
|
||||||
if (!keyPair) {
|
|
||||||
throw new FieldError({
|
|
||||||
field: 'deviceId',
|
|
||||||
message: 'Unauthorized Device ID detected'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const processedPrivateKey = processKey(keyPair.privateKey)
|
|
||||||
const decryptedMessage = await ECCrypto.decrypt(
|
|
||||||
processedPrivateKey,
|
|
||||||
convertToEncryptedMessage(encryptedMessage)
|
|
||||||
)
|
|
||||||
const parsedMessage = decryptedMessage.toString('utf8')
|
|
||||||
|
|
||||||
return parsedMessage
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(err)
|
|
||||||
if (err.message?.toLowerCase() === 'bad mac') {
|
|
||||||
logger.error(
|
|
||||||
'Bad Mac!',
|
|
||||||
err,
|
|
||||||
convertToEncryptedMessage(encryptedMessage),
|
|
||||||
!!keyPair
|
|
||||||
)
|
|
||||||
}
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
isAuthorizedDevice,
|
|
||||||
isEncryptedMessage,
|
|
||||||
generateKeyPair,
|
|
||||||
encryptMessage,
|
|
||||||
decryptMessage,
|
|
||||||
authorizeDevice,
|
|
||||||
generateRandomString,
|
|
||||||
nodeKeyPairs,
|
|
||||||
devicePublicKeys
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue