Secure IoT command sending (NodeJS Client). There are many ways to secure and authenticate a networking communication, but not all solutions will run on a microcontroller, where processing power and memory is a scarce resource.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

118 lines
3.8 KiB

const net = require('net');
var crypto = require('crypto');
function secureSend(options, cmdSend, cb){
const promise = new Promise(function(resolve, reject){
let reqHash = crypto.createHmac('sha256', options.hmacKey);
reqHash.setEncoding('binary');
let resHash = Buffer.allocUnsafe(options.hashByteLength);
let gotBytes = Buffer.allocUnsafe(options.randDataLength);
let gotBytesNum = 0;
let res = null; // The final analyzed response
var client = new net.Socket();
client.connect(options.port, options.ip, ()=>{
gotBytesNum = 0;
if(typeof options.onConnect === 'function') options.onConnect();
client.write('X'); //necessary for the arduino ethernet library
});
client.on('error', (err) => {
reject(err);
});
client.on('close', function() {
if(res === null) reject(new Error('Connection closed before the response was sent.'));
else resolve(res);
});
client.on('data', (data) => {
gotBytesNum += data.length;
if(gotBytesNum <= options.randDataLength){
reqHash.update(Buffer.from(data), 'binary');
for(i=0; i<data.length; i++){
gotBytes[gotBytesNum - data.length + i] = data[i];
}
}
if(gotBytesNum == options.randDataLength){
reqHash.update(Buffer.from([cmdSend.cmd]), 'binary');
reqHash.end(); // very important! You cannot read from the stream until you have called end()
var sha256sum = reqHash.read();
client.write(sha256sum, 'binary');
//printHash(sha256sum);
}
if(gotBytesNum > options.randDataLength){
if (gotBytesNum > (options.randDataLength + options.hashByteLength)) reject(new Error('Server returned more bytes than expeced'));
for(i=0; i<data.length; i++)
resHash[gotBytesNum - options.randDataLength - data.length + i] = data[i];
if (gotBytesNum == (options.randDataLength + options.hashByteLength)){
res = analyzeResponse(options.hmacKey, cmdSend, gotBytes, resHash);
client.destroy(); // kill client after server's response
}
}
});
});
if (cb && typeof cb == 'function'){
promise.then(cb.bind(null, null), cb);
}
return promise;
}
function analyzeResponse(hmacKey, cmdSend, gotBytes, resHash){
for(i=0; i<cmdSend.responses.length; i++){
let testHash = Buffer.from(getHash(gotBytes, hmacKey, [cmdSend.responses[i]]), 'binary');
if (hashesAreTheSame(resHash, testHash)){
return cmdSend.responses[i];
}
}
return undefined;
}
function getHash(data, key, additionalData){
let checkHash = crypto.createHmac('sha256', key);
checkHash.setEncoding('binary');
checkHash.update(data, 'binary');
checkHash.update(Buffer.from(additionalData));
checkHash.end();
return checkHash.read();
}
function hashesAreTheSame(hash1, hash2){
if (hash1.length != hash2.length) return false;
for (j=0; j<hash1.length; j++) if(hash1[j] != hash2[j]) return false;
return true;
}
// function printHash(hash){
// let str = '';
// for (i=0; i<hash.length; i++)
// str += hash.charCodeAt(i).toString(16);
// console.log(str);
// }
// function toHexString(byteArray) {
// return Array.from(byteArray, function(byte) {
// return ('0' + (byte & 0xFF).toString(16)).slice(-2);
// }).join('');
// }
module.exports = secureSend;