/**
* Encryption protocol for starwave
* new fields in message object:
* encrypted - means that message is encrypted
* publicKey - public key of the sender which wants to make crypted tunnel
* Using secp256k1 curve and aes256 algorithm as default
*
* This module uses elliptic library for creating keypair and crypto-js to encrypt/decrypt message
* https://github.com/indutny/elliptic
* https://github.com/brix/crypto-js
*/
'use strict';
//unify browser and node
if (typeof _this ==='undefined') {
var _this = this;
}
if (_this.window === undefined){
_this.elliptic = require('elliptic');
_this.CryptoJS = require('crypto-js');
}
const SWCRYPTO_CONNECTION_MESSAGE_TYPE = 'DH-CONNECTION';
/**
* Crypto addon for StarWave Protocol
*/
class StarwaveCrypto {
constructor(starwaveProtocolObject, secretKeysKeyring, curve = 'secp256k1') {
if (_this.window === undefined){
this.elliptic = require('elliptic');
this.CryptoJS = require('crypto-js');
} else {
this.elliptic = elliptic;
this.CryptoJS = CryptoJS;
}
let that = this;
// EСDH object
this.ec = new elliptic.ec(curve);
this.keyObject = this.ec.genKeyPair();
this.public = this.generateKeys();
this.starwave = starwaveProtocolObject;
this.secretKeys = secretKeysKeyring;
this.curve = curve;
this._connectionsCallbacks = {};
starwaveProtocolObject.registerMessageHandler(SWCRYPTO_CONNECTION_MESSAGE_TYPE, function (message) {
that.handleIncomingMessage(message);
return true;
});
starwaveProtocolObject.registerMessageHandler('', function (message) {
that.handleIncomingMessage(message);
return false;
});
};
/**
* Get public key object for Diffie-Hellman
* @returns {*}
*/
generateKeys(keyObject = this.keyObject) {
return keyObject.getPublic(true, 'hex');
}
/**
* Create secret key based on external public key
* @param {string} externalPublic
* @returns {string}
*/
createSecret(externalPublic) {
let secret;
try {
let pub = this.ec.curve.decodePoint(externalPublic, 'hex'); //get public key object from string
secret = this.keyObject.derive(pub);
secret = secret.toString(16);
} catch (err) {
console.log('Error: Can not create secret key ' + err); //if externalPublic is wrong
}
return secret;
};
/**
* Encrypts data
* @param {string} data Data to encrypt
* @param {string} secret Encryption secret
* @returns {string}
*/
cipherData(data, secret) {
let encrypted = CryptoJS.AES.encrypt(data, secret).toString(); //base64
let b64 = CryptoJS.enc.Base64.parse(encrypted);//object
encrypted = b64.toString(CryptoJS.enc.Hex);//hex
return encrypted;
}
/**
* Decripts data
* @param {string} encryptedData Encrypted data source
* @param {string} secret Encryption secret
* @returns {string}
*/
decipherData(encryptedData, secret) {
let data;
try {
//unpack from hex to native base64 and to object
let b64 = CryptoJS.enc.Hex.parse(encryptedData);
let bytes = b64.toString(CryptoJS.enc.Base64);
data = CryptoJS.AES.decrypt(bytes, secret);
data = data.toString(CryptoJS.enc.Utf8);
} catch (e) {
console.log('Error decrypting data: ' + e)
}
return data;
}
/**
* Decrypts data field in message
* @param {object} message Message object
* @returns {*}
*/
decipherMessage(message) {
let decryptedData;
//if message didn't encrypted, return data
if(!message.encrypted) {
return decryptedData = message.data;
}
//if we have secret key associated with this socket than we have the tunnel
if(this.secretKeys[message.sender]) {
decryptedData = this.decipherData(message.data, this.secretKeys[message.sender]);
if(decryptedData) {
//try to parse json
try {
decryptedData = JSON.parse(decryptedData)
} catch (e) {
console.log("Error: An error occurred while parsing decrypted text: " + e);
}
}
message.original = message.data;
message.data = decryptedData;
delete message.encrypted;
}
return decryptedData;
}
/**
* Encrypts data field in message
* @param {object} message Message object
* @returns {*}
*/
cipherMessage(message) {
let cryptedData;
//should be assotiated secret key and check that message has not been already encrypted
if(this.secretKeys[message.reciver] && !message.encrypted) {
cryptedData = this.cipherData(JSON.stringify(message.data), this.secretKeys[message.reciver]);
message.encrypted = true;
message.data = cryptedData;
}
return cryptedData;
}
/**
* Processes incoming message
* @param message
* @returns {*}
*/
handleIncomingMessage(message) {
//watch if the message has Public key field then the message is only for sending the key
if(message.publicKey) {
let sc = this.createSecret(message.publicKey); //exclude situation with exception on wrong public
//if we don't have secret key then we save sender publickey and create secret and send our public to make connection
if((!this.secretKeys[message.sender] && sc) || this.secretKeys[message.sender] !== sc) {
this.makeConnection(message.sender);
}
if(sc) {
this.secretKeys[message.sender] = sc;
if(this._connectionsCallbacks[message.sender]) {
this._connectionsCallbacks[message.sender](message.sender, sc);
delete this._connectionsCallbacks[message.sender];
}
}
delete message.publicKey;
return 0;
}
message.data = this.decipherMessage(message); //undefined if we have errors
return 1;
}
/**
* Make encrypted connection
* @param {string} messageBus Connection address
* @param {(function(string, string))} cb Connection callback 1 - receiver, 2 - secret key
* @return {StarwaveCrypto}
*/
makeConnection(messageBus, cb) {
let that = this;
if(cb) {
this._connectionsCallbacks[messageBus] = cb;
//Call callback if key already created
if(this.secretKeys[messageBus]) {
setTimeout(function () {
cb(messageBus, that.secretKeys[messageBus]);
}, 1);
}
}
let message = this.starwave.createMessage('{}', messageBus, undefined, SWCRYPTO_CONNECTION_MESSAGE_TYPE);
message.publicKey = this.public;
this.starwave.sendMessage(message);
return this;
}
/**
* Send message using encryption protocol
* @param {object} message Data for sending
* @returns {number} Status
*/
sendMessage(message) {
//check if we have secret key for reciver
let sk = this.secretKeys[message.reciver]; //secret key
if(sk) {
if(this.cipherMessage(message)) {
this.starwave.sendMessage(message);
return message;
} else {
console.log("Error: Can't cipher message");
return 2;
}
}
else {
console.log(`Error: There is no secret key for address: ${message.reciver}`);
return 1;
}
}
}
//unify browser and node
if (this.window === undefined){
module.exports = StarwaveCrypto;
}