Skip to content

Instantly share code, notes, and snippets.

@hitman401
Last active December 10, 2015 15:15
Show Gist options
  • Save hitman401/0e6cfb7e2d1b7fff0dfd to your computer and use it in GitHub Desktop.
Save hitman401/0e6cfb7e2d1b7fff0dfd to your computer and use it in GitHub Desktop.
Handshake example code nodejs
var sodium = require('libsodium-wrappers');
var log = require('npmlog');
// These are the commandline parameters from the launcher while starting the application
// Assign these values at run time, if trying this example
var HOST, PORT, LAUNCHER_NONCE;
var ResponseBufferHandler = function(onResponseCallback) {
var bufferQueue = [];
var processing = false;
var expectedLength = 0;
var response = [];
var responseCallback = onResponseCallback || function() {/* no-op */};
var readyToReadResponse = true;
var addToNextBuffer = function(buffer) {
bufferQueue[0] = bufferQueue.length === 0 ? buffer : Buffer.concat([buffer, bufferQueue[0]]);
};
var reset = function (buff) {
var lengthBuff = buff.slice(0, 8);
buff = buff.slice(8);
var uintArray = new Uint8Array(lengthBuff);
var view = new DataView(uintArray.buffer);
expectedLength = view.getUint32(0, true);
response = [];
if (buff.length > 0) {
addToNextBuffer(buff);
}
};
var read = function(buff) {
if (buff.length > expectedLength) {
addToNextBuffer(buff.slice(expectedLength));
buff = buff.slice(0, expectedLength);
}
expectedLength -= buff.length;
response = response.concat(buff);
return expectedLength <= 0;
};
var run = function() {
try {
while (bufferQueue.length !== 0) {
var buff;
if (bufferQueue[0].length < 8 && readyToReadResponse && bufferQueue.length === 1) {
continue;
}
buff = bufferQueue.splice(0, 1)[0];
if (readyToReadResponse) {
readyToReadResponse = false;
reset(buff);
} else if (read(buff)) {
responseCallback(response);
readyToReadResponse = true;
}
}
} catch(e) {
console.log(e);
}
processing = false;
};
this.addBuffer = function(buff) {
bufferQueue.push(buff);
if (!processing) {
processing = true;
run();
}
};
return this;
};
var Connection = function() {
var net = require('net');
var log = require('npmlog');
var socket;
var onDataReceivedListener;
var onConnectionErrorListener;
var alive = false;
var HOST = 'localhost';
var LENGTH_SIZE = 8;
var setOnDataRecievedListener = function(listnerCallback) {
onDataReceivedListener = listnerCallback;
};
var setOnConnectionErrorListener = function(listnerCallback) {
onConnectionErrorListener = listnerCallback;
};
var OPTIONS = {
'onData': setOnDataRecievedListener,
'onError': setOnConnectionErrorListener
};
var setFromOptions = function(options) {
for (var key in OPTIONS) {
if (!options.hasOwnProperty(key)) {
continue;
}
OPTIONS[key](options[key]);
}
};
var onDataReceived = function(data) {
log.verbose('Data received from launcher');
if (!onDataReceivedListener) {
return;
}
responseBuffer.addBuffer(data);
};
var onResponse = function(response) {
onDataReceivedListener(response);
};
var responseBuffer = new ResponseBufferHandler(onResponse);
var onConnectionError = function() {
var connectionDroppedMsg = 'Connection with the launcher disconnected';
var couldNotEstablishConMsg = 'Connection could not be established';
log.error(alive ? connectionDroppedMsg : couldNotEstablishConMsg);
alive = false;
if (!onConnectionErrorListener) {
return;
}
onConnectionErrorListener();
};
/**
* connect function is invoked to connect to a port on the local machine
* callback will be invoked only when the connection is successful
* else the onClosed listener will be invoked if the connection fails
*/
this.connect = function(host, portNumber, callback, options) {
if (options) {
setFromOptions(options);
}
host = host || HOST;
log.verbose('Connecting with the launcher at - ' + host + ':' + portNumber);
socket = new net.Socket();
socket.on('data', onDataReceived);
//socket.on('error', onConnectionError);
socket.on('close', onConnectionError);
socket.connect(portNumber, host, function() {
alive = true;
log.verbose('Connected with the launcher at port : ' + portNumber);
callback();
});
};
/**
* Write data to the launcher TCP connection
* Length(U64INT - Little Endian format) of the data is prepended as before the data.
* @param data - Object
*/
this.send = function(data) {
var lengthAsLE = new Buffer(LENGTH_SIZE);
lengthAsLE.fill(0);
lengthAsLE.writeUInt32LE(data.length);
socket.write(lengthAsLE);
socket.write(data);
};
this.isAlive = function() {
return alive;
};
this.setOnDataRecievedListener = setOnDataRecievedListener;
this.setOnConnectionErrorListener = setOnConnectionErrorListener;
};
var handshakeKeys = sodium.crypto_box_keypair();
var handshakeNonce = sodium.randombytes_buf(sodium.crypto_box_NONCEBYTES);
var launcherConnection = new Connection();
var onConnectionError = function(err) {
log.error(err);
};
var onHandShakeComplete = function(handshakeResponse) {
handshakeResponse = JSON.parse(handshakeResponse);
var launcherPublicKey = new Uint8Array(new Buffer(handshakeResponse.data.launcher_public_key, 'base64'));
var uint8Array = new Uint8Array(new Buffer(handshakeResponse.data.encrypted_symm_key, 'base64'));
var decryptedSymmKey = sodium.crypto_box_open_easy(uint8Array, handshakeNonce, launcherPublicKey, handshakeKeys.privateKey);
var encryptionNonce = decryptedSymmKey.subarray(0, sodium.crypto_secretbox_NONCEBYTES);
var encryptionKey = decryptedSymmKey.subarray(sodium.crypto_secretbox_NONCEBYTES);
log.info('Symmetric keys for Communication obtained - Handshake completed');
};
var startHandshake = function() {
var assymNonce = new Buffer(handshakeNonce).toString('base64');
var publicKey = new Buffer(handshakeKeys.publicKey).toString('base64');
var request = {
"endpoint": "safe-api/v1.0/handshake/authenticate-app",
"data": {
"launcher_string": LAUNCHER_NONCE,
"asymm_nonce": assymNonce,
"asymm_pub_key": publicKey
}
};
launcherConnection.send(JSON.stringify(request));
};
launcherConnection.connect(HOST, PORT, startHandshake, {
'onData': onHandShakeComplete,
'onError': onConnectionError
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment