Skip to content

Instantly share code, notes, and snippets.

@isstaif
Last active June 9, 2016 21:09
Show Gist options
  • Save isstaif/7722409 to your computer and use it in GitHub Desktop.
Save isstaif/7722409 to your computer and use it in GitHub Desktop.
TCP public chat, Node.js
var net = require('net')
, encryption = require('./../encryption')
, ursa = require('ursa')
, exec = require('child_process').exec
, fs = require('fs')
, sh = require('execSync')
var stdin = process.openStdin()
var username, password, connection, privateKey, publicKey, otherPublicKey = false, userNames = [], certificate = false, myId, isNew = true
privateKey = ursa.generatePrivateKey()
publicKey = privateKey.toPublicPem().toString()
fs.writeFileSync('private-key.pem', privateKey.toPrivatePem().toString())
/**************
GENERATING CERTIFICATE REQUEST AND SENDING IT TO THE CERTIFICATE AUTHOROTY
***************/
sh.exec("openssl req -new -key private-key.pem -out request.pem -subj /C=SY/ST=Syria/L=Damascus/O=A/emailAddress=a@a.com")
var request = fs.readFileSync('request.pem').toString()
var caServer = net.connect(5000, function(){
caServer.write(request)
caServer.on('data', function(data){
//getting certificate
var res = sh.exec('openssl x509 -req -in request.pem -signkey private-key.pem -out certificate.pem')
certificate = fs.readFileSync('certificate.pem').toString()
//AFTER WE GET A CERTIFICATE, WE CONNECT TO THE CHAT SERVER
connectServer()
})
})
var receiveId = false
var otherPublicKeys = []
var sessionKey = 'asdf'
//THIS FUNCTION IS USED TO INITIALIZE THE CONNECTION BETWEEN THE CLIENT AND SERVER
function initialization(){
//WE FIRST SEND THE PUBLIC KEY TO THE SERVER
connection.write(publicKey)
//THEN WE GET INPUT FROM THE USER
console.log('Please enter your username!')
stdin.addListener('data', function(data){
//WE RECEIVE THE USERNAME
if (!username){
username = data
connection.write(username)
console.log('Please enter your password')
//THEN WE RECEIVE THE PASSWORD AND SEND IT
} else if (!password) {
password = data
connection.write(password)
//THEN WE FINALLY START SENDING DATA ENCRYPTED WITH THE SESSION KEY, ATTACHED WITH OUR ID AND OUR SIGNITURE
//FORMAT: [signiature:userId:encryptedMessage]
} else {
connection.write(encryption.sign(data, privateKey) + ':' + myId.toString() + ':' +encryption.encryptAES(data, sessionKey))
}
})
}
/*
FUNCTION TO PROCESS INCOMING DATA
WE HAVE 4 BASIC INCOMING
1) PUBLIC KEYS
whenever a new user joins the chat group, we send his public key to all other connecting clients
so that these clients can use this key to verify the digital signed messages sent by this client
each public key is attached with the client id that it belongs to in addition to the username of
this client
Multiple keys are sent in the following format
-----BEGIN PUBLIC KEY-----
XXXXXXXXXXXXXXXXXXXXXXXXX
-----END PUBLIC KEY-----
username1
-----END PUBLIC KEY-----
id1
-----BEGIN PUBLIC KEY-----
YYYYYYYYYYYYYYYYYYYYYYYYYY
-----END PUBLIC KEY-----
username2
-----END PUBLIC KEY-----
id2
====
2) SESSION KEY
whever a new client joins the chat group, his is sent a sessionKey that he should use to encrypt
that he should use to encrypt and decrypt all outgoing and incoming messages
3) CLIENT ID
each client has a unique id that he receives from the server. if the client has an id of 0, then
he is the first client , and he is responsible for generating and distributing the sessino key
4) CHAT MESSAGES
after the chat user finally gets the session key, he starts sending and receiveing chat message.
each message is composed of three parts: [signiture:userId:encryptedMessage]
*/
function input(data){
var parsed = data.toString()
var type = parsed.substring(0, 3)
var string = parsed.substring(3, parsed.length)
if (type === 'pub'){
string.split('-----BEGIN PUBLIC KEY-----\n').forEach(function(message){
if (message.length > 1){
var isCertificate = true
var isName = true
var key, receivedIndex, receivedName
message.split('-----END PUBLIC KEY-----\n').forEach(function(message){
if (isCertificate){
message = '-----BEGIN PUBLIC KEY-----\n' + message + '-----END PUBLIC KEY-----\n'
key = ursa.createPublicKey(message)
isCertificate = false
} else if (isName) {
receivedName = message
isName = false
} else {
receivedIndex = parseInt(message)
otherPublicKeys[receivedIndex] = key
userNames[receivedIndex] = receivedName
isCertificate = true
isName = true
}
})
console.log('========================================')
console.log('SERVER: receiving key of client ' + receivedIndex)
console.log(key.toPublicPem().toString().slice(0, -1))
console.log('========================================')
//IF I AM THE SESSION ADMIN, I SHOULD SEND THE SESSION KEY TO THE NEW CONNECTING CLIENT
if (myId == 0 && receivedIndex != 0){
connection.write('sessionKey:' + receivedIndex + ':'+ encryption.encrypt(sessionKey, key))
}
}
})
} else if (parsed.indexOf('sessionKey') >= 0){
var result = string.split(':')
sessionKey = encryption.decrypt(result[2], privateKey)
console.log('========================================')
console.log('SERVER: receiving session key: ' + sessionKey)
console.log('========================================')
} else if (isNew) {
myId = parseInt(data.toString())
console.log('SERVER: Your ID is: '+myId)
isNew = false
} else {
var triple = data.toString().split(':') //converting the received buffer to a string
var signiture = triple[0]
var userId = parseInt(triple[1])
var string = triple[2]
var decryptedString = encryption.decryptAES(string, sessionKey)
if (encryption.verify(decryptedString, signiture, otherPublicKeys[userId])){
//removing /n character
console.log("| " + userNames[userId] + ': ' + decryptedString.substr(0, decryptedString.length-1))
} else {
console.log('Error! The sent string has been changed!!!')
}
}
}
function connectServer(){
connection = net.connect(4000, "localhost", initialization)
connection.on('data', input)
}
var ursa = require('ursa');
var fs = require("fs");
var crypto =require('crypto')
//var plaintext = "test";
/*-----------------------------------------------------------*
* ENCRYPT: RSA 1024 bit *
*-----------------------------------------------------------*/
/*
var privateKey = ursa.generatePrivateKey()
var publicKey = ursa.createPublicKey(privateKey.toPublicPem().toString())
var cipertext = _encrypt(plaintext, publicKey);
// verification
var result = _verify(plaintext, cipertext, privateKey);
if (result == false) {
console.log("encryption failed!!!");
}
console.log(result) */
/********************************************************************
IMPLEMENTING FUNCTIONS...
********************************************************************/
exports.encrypt = function (plaintext, key)
{
return key.encrypt(plaintext, 'utf8', 'hex', ursa.RSA_PKCS1_OAEP_PADDING)
}
exports.decrypt = function (encoded, key)
{
return key.decrypt(encoded, 'hex', 'utf8', ursa.RSA_PKCS1_OAEP_PADDING)
}
exports.encryptAES = function (text, key){
var cipher = crypto.createCipher('aes-256-cbc', key)
var crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
return crypted;
}
exports.decryptAES = function(text, key){
var decipher = crypto.createDecipher('aes-256-cbc', key)
var dec = decipher.update(text,'hex','utf8')
dec += decipher.final('utf8');
return dec;
}
exports.sign = function (data, privateKey){
var signer = ursa.createSigner('sha256')
signer.update(data, 'utf8')
return signer.sign(privateKey, 'hex')
}
exports.verify = function(data, signiture, publicKey){
var verifier = ursa.createVerifier('sha256')
verifier.update(data, 'utf8')
return verifier.verify(publicKey, signiture, 'hex')
}
var net = require('net')
var server = new net.Server()
, async = require('async')
var users = [
{ username : 'john', pass : 'john' },
{ username : 'bob', pass : 'bob'},
{ username : 'steve', pass : 'steve' },
{ username : 'amjad', pass : 'amjad' }
]
var idCount = 0
server.on('connection', function (client) {
var currentUser = {}
currentUser.client = client
currentUser.authenticated = false
currentUser.publicKey = false
//on each connection
client.on('data', function(data){
var string = data.toString().substring(0, data.length-1)
//STEP1: the first thing we receive is the public key of our client
if (!currentUser.publicKey){
console.log(string)
currentUser.publicKey = string
//STEP2: then we receive his username and check with it is in our database or not
} else if (!currentUser.username){
var isValidUser = false
for (var i = 0; i < users.length; i++) {
if (users[i].username === string){
isValidUser = true
currentUser.username = string
currentUser.index = i
users[i].client = client
users[i].publicKey = currentUser.publicKey
}
}
if (!isValidUser){
console.log('invalid user')
currentUser.client.end("Invalid username")
}
//STEP3: then we receive his password and see it is valid
} else if (currentUser.authenticated == false){
if (users[currentUser.index].pass === string){
console.log("Successful authentication")
currentUser.authenticated = true
//ASSIGN AN ID: if the user is valid we assign him with a special ID and give that id to him
currentUser.id = users[currentUser.index].id = idCount;
currentUser.client.write(idCount.toString())
idCount ++;
//DISTRIBUTE MY PUB KEY: we send his public key to the other connecting clients
users.forEach(function(receiver){
if (receiver.client && receiver.client != currentUser.client){
var message = "pub"
message += currentUser.publicKey + '\n'
message += users[currentUser.index].username
message += "-----END PUBLIC KEY-----\n"
message += currentUser.id.toString()
receiver.client.write(message)
}
})
//SEND ME OTHER'S KEYS: then we send the new client the public keys of previous connected clients
var message = 'pub'
var empty = true
for (var i = 0; i < users.length; i++) {
var receiver = users[i]
if (receiver.client && (receiver.publicKey != false) ) {
message += receiver.publicKey + '\n'
message += receiver.username
message += "-----END PUBLIC KEY-----\n"
message += receiver.id.toString() + '\n'
empty = false
}
}
if (!empty){
currentUser.client.write(message)
}
} else {
console.log("Wrong password")
currentUser.client.end("Wrong password")
}
//STEP4: PASS MESSAGES: after the user has authenticated, we pass messages sent by other users
} else {
//PASSING SESSION KEY MESSAGES: in case the message starts with 'sessionKey', then we are going to pass it to only one new client
var string = data.toString()
if (string.indexOf('sessionKey') >= 0){
var result = string.split(':')
console.log('passing session to:')
console.log(result[1])
var sessionUser
users.forEach(function(user){
if (user.id == result[1]) sessionUser = user
})
sessionUser.client.write(data)
//PASSING NORMAL MESSAGES: this is the default case, where we broadcast the received message to all connecting clients
} else {
users.forEach(function(receiver){
if (receiver.client)
receiver.client.write(data)
})
}
}
})
})
server.listen(4000)
@NKjoep
Copy link

NKjoep commented May 1, 2014

Very useful to understand the full stack with crypto and keys. Great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment