Last active
August 29, 2015 14:17
-
-
Save bellbind/63fd853a8d0119df7848 to your computer and use it in GitHub Desktop.
[webcrypto][websocket][iojs] RSA crypted message chat
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<title>WebCrypto and WebSocket</title> | |
<script src="script.js"></script> | |
</head> | |
<body> | |
<div> | |
[<input id="name" type="text" value="anon" style="width: 100px" | |
readonly="readonly" />] | |
<input id="message" type="text" style="width: 300px" /> | |
<button id="send" disabled="disabled">send to</button> | |
<select id="targets"></select> | |
</div> | |
<div> | |
<ul id="log" style="overflow: auto; width: 800px; height: 300px"></ul> | |
</div> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "webcrypto-chat", | |
"version": "0.0.1", | |
"description": "crypted chat with websocket server", | |
"dependencies": { | |
"faye-websocket": "*", | |
"mime-types": "*" | |
}, | |
"scripts": { | |
"start": "iojs ws-server.js" | |
}, | |
"engines": { | |
"iojs": "1.x" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
web: npm start |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;window.addEventListener("load", function () { | |
"use strict"; | |
// helpers for ArrayBuffer representation | |
var s2b = function (str) { | |
return new Promise(function (fulfill, reject) { | |
var file = new FileReader(); | |
file.addEventListener("load", function (ev) { | |
fulfill(ev.target.result); | |
}, false); | |
file.addEventListener("error", reject, false); | |
file.addEventListener("abort", reject, false); | |
file.readAsArrayBuffer(new Blob([str])); | |
}); | |
}; | |
var b2s = function (buffer) { | |
return new Promise(function (fulfill, reject) { | |
var file = new FileReader(); | |
file.addEventListener("load", function (ev) { | |
fulfill(ev.target.result); | |
}, false); | |
file.addEventListener("error", reject, false); | |
file.addEventListener("abort", reject, false); | |
file.readAsText(new Blob([buffer])); | |
}); | |
}; | |
var b2h = function (buffer, conn) { | |
conn = conn === undefined ? "" : conn; | |
return [].map.call(new Uint8Array(buffer), function (b, i) { | |
var c = b.toString(16); | |
return c.length === 1 ? "0" + c : c; | |
}).join(conn); | |
}; | |
// crypto specs and key preparaion | |
var system = { | |
name: "RSA-OAEP", | |
modulusLength: 2048, | |
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), | |
hash: {name: "SHA-256"}}; | |
var spec = { | |
name: system.name, | |
hash: system.hash, | |
}; | |
var alg = { | |
name: system.name, | |
}; | |
var fmt = "jwk"; | |
crypto.subtle.generateKey(system, true, [ | |
"encrypt", "decrypt" | |
]).then(function (keys) { | |
return Promise.all( | |
[keys.privateKey, crypto.subtle.exportKey(fmt, keys.publicKey)]); | |
}).then(function (keys) { | |
return Promise.all([ | |
keys[0], keys[1], s2b(JSON.stringify(keys[1])).then( | |
crypto.subtle.digest.bind(crypto.subtle, {name: "SHA-256"}) | |
).then(function (raw) { | |
//return btoa(Array.apply(null, new Uint8Array(raw))); | |
return b2h(raw); | |
}), | |
]); | |
}).then(function (keys) { | |
openSocket({id: keys[2], privKey: keys[0], pubJwk: keys[1]}); | |
}); | |
var openSocket = function openSocket(opts) { | |
// ui and state | |
var name = document.getElementById("name"); | |
var message = document.getElementById("message"); | |
var log = document.getElementById("log"); | |
var send = document.getElementById("send"); | |
var targets = document.getElementById("targets"); | |
var pubkeys = []; | |
while (targets.firstChild) targets.removeChild(targets.firstChild); | |
name.value = opts.id.substring(0, 8); | |
var wsurl = location.origin.replace(/^http/, "ws") + "/bs/"; | |
var socket = new WebSocket(wsurl); | |
socket.addEventListener("open", function (ev) { | |
console.log("[open]"); | |
send.addEventListener("click", sender, false); | |
// send my pubkey | |
socket.send(JSON.stringify({ | |
t: "hello", id: opts.id, key: opts.pubJwk | |
})); | |
send.disabled = false; | |
}, false); | |
socket.addEventListener("message", function (ev) { | |
var msg = JSON.parse(ev.data); | |
if (msg.t === "hello") return acceptHello(msg); | |
if (msg.t === "chat") return acceptChat(msg); | |
if (msg.t === "bye") return acceptBye(msg); | |
}, false); | |
socket.addEventListener("close", function (ev) { | |
console.log("[close]"); | |
send.removeEventListener("click", sender, false); | |
// reconnect | |
requestAnimationFrame(openSocket.bind(null, opts)); | |
}, false); | |
// actions | |
var sender = function (ev) { | |
var pubkey = pubkeys[targets.selectedIndex]; | |
if (!pubkey) return; | |
console.log("[send]"); | |
var msg = "[" + name.value + "] " + message.value; | |
// encrypt a message to the specified receiver | |
s2b(msg).then( | |
crypto.subtle.encrypt.bind(crypto.subtle, alg, pubkey.key) | |
).then(function (crypted) { | |
var array = Array.apply(null, new Uint8Array(crypted)); | |
console.log("[crypted]", array); | |
socket.send(JSON.stringify({t: "chat", chat: array})); | |
var li = document.createElement("li"); | |
li.textContent = msg + | |
" (sent to " + pubkey.id.substring(0, 8) + ")"; | |
log.insertBefore(li, log.firstChild); | |
}); | |
}; | |
var acceptHello = function (msg) { | |
// store joined receiver key | |
crypto.subtle.importKey(fmt, msg.key, spec, false, [ | |
"encrypt" | |
]).then(function (pubkey) { | |
console.log("[import key]", pubkey); | |
pubkeys.push({id: msg.id, key: pubkey}); | |
var opt = document.createElement("option"); | |
opt.textContent = msg.id; | |
opt.id = msg.id; | |
targets.add(opt); | |
}); | |
}; | |
var acceptChat = function (msg) { | |
// try to decrypt message | |
crypto.subtle.decrypt( | |
alg, opts.privKey, new Uint8Array(msg.chat) | |
).then(b2s).then(function (message) { | |
console.log("[message] " + message); | |
var li = document.createElement("li"); | |
li.textContent = message; | |
log.insertBefore(li, log.firstChild); | |
}, function (err) { | |
// its not for me | |
console.log("[decrypt]", err); | |
}); | |
}; | |
var acceptBye = function (msg) { | |
// remove receiver list | |
for (var i = 0; i < pubkeys.length; i++) { | |
if (pubkeys[i].id == msg.id) { | |
pubkeys.splice(i, 1); | |
break; | |
} | |
} | |
targets.removeChild(document.getElementById(msg.id)); | |
}; | |
}; | |
}, false); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Simple Broadcast WebSocket server | |
// $ npm install faya-websocket mime-types | |
// $ iojs ws-server.js | |
var http = require("http"); | |
var fs = require("fs"); | |
var path = require("path"); | |
// 3rd party libs | |
var WebSocket = require("faye-websocket"); // W3C API compliant interface | |
var mime = require("mime-types"); | |
var fileServer = function (req, res) { | |
console.log("[file server]", req.method, req.url); | |
if (req.method !== "GET") { | |
res.writeHead(405, {allow: "GET"}); | |
return res.end(); | |
} | |
var loc = req.url.endsWith("/") ? req.url + "index.html" : req.url; | |
var file = path.join(__dirname, loc); | |
fs.createReadStream(file).once("readable", function () { | |
console.log("[exist]", file); | |
var mtype = mime.lookup(loc) || "application/octet-stream"; | |
var charset = mime.charset(mtype); | |
var ctype = mtype + (charset ? "; charset=" + charset : ""); | |
res.writeHead(200, {"content-type": ctype}); | |
this.pipe(res); | |
}).once("error", function (er) { | |
console.log("[not exist]", file); | |
res.writeHead(404); | |
res.end(); | |
}); | |
}; | |
// single broadcast hub | |
var BroadcastServer = function () { | |
this.sockets = new Map(); | |
}; | |
BroadcastServer.prototype.onUpgrade = function (req, socket, head) { | |
console.log("[upgrade]", req.method, req.url); | |
var sockets = this.sockets; | |
var ws = new WebSocket(req, socket, head); | |
ws.addEventListener("open", function (ev) { | |
console.log("[open]", ws.url); | |
for (var val of sockets.values()) { | |
//console.log(val); | |
if (val.hello) ws.send(val.hello); | |
} | |
sockets.set(ws, {}); | |
}, false); | |
ws.addEventListener("message", function (ev) { | |
console.log("[message]", ev.data); | |
var msg = JSON.parse(ev.data); | |
if (msg.t === "hello") { | |
console.log("[hello]", msg.id); | |
sockets.set(ws, {id: msg.id, hello: ev.data}); | |
} | |
for (var socket of sockets.keys()) { | |
socket.send(ev.data); | |
} | |
}, false); | |
ws.addEventListener("close", function (ev) { | |
console.log("[close]", ws.url); | |
var id = sockets.get(ws).id; | |
sockets.delete(ws); | |
for (var socket of sockets.keys()) { | |
socket.send(JSON.stringify({t: "bye", id: id})); | |
} | |
console.log("[bye]", id); | |
}, false); | |
}; | |
var bs = new BroadcastServer(); | |
var server = http.createServer(fileServer); | |
server.on("upgrade", bs.onUpgrade.bind(bs)); | |
var port = process.env.PORT || 8080; | |
server.listen(port); | |
console.log("open http://localhost:" + port + "/"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Check your browser&platform interoperability