nadachat
Version:
simple secure chat with end to end encyption
136 lines (101 loc) • 4.49 kB
JavaScript
var window=self; // fools sjcl into thinking we are running in a browser scope instead of a worker scope
// this is now injected as a string to avoid net io / log entries:
// importScripts('https://nadachat.com/js/sjcl-core.js'); //make with directions on https://github.com/bitwiseshiftleft/sjcl/wiki/Getting-Started, adding --with-ecc to configure as the only change, then taking core.js
// over-ride ajcl AES defaults with stronger values:
sjcl.json.defaults.ks=256;
sjcl.json.defaults.ts=128;
// add entropy to the fortuna instance used by sjcl:
sjcl.random.addEntropy(STAMP); // use entropy from PHP and DOM
sjcl.random.addEntropy(stamp()); // use as much local entropy as possible
sjcl.random.addEntropy(Math.random().toString(2).split("").filter(/./.test, /01/).length); // mostly because it does a ms or two of work to spin following timers
sjcl.random.addEntropy(getRandomFromCrypto(94)); // use OS=provided values, munged with timing data
sjcl.random.addEntropy(location.href); // use blob url for unpredictable values
sjcl.random.addEntropy(timeserial()+timeserial()); // yet more timing data
// sends a response or error back to the web app:
function send(type, data){
self.postMessage({type:type, data: data});
}
self.addEventListener("message", function(evt) {
sjcl.random.addEntropy(stamp());
var e = evt.data;
if(e.data && e.data.STAMP) sjcl.random.addEntropy(e.data.STAMP); // used if passing in more late-gathered entopy (like motion data)
if(e.type == "ecc") { // generate a new ecc curve keypair
var pair = sjcl.ecc.elGamal.generateKeys(sjcl.ecc.curves.c521); // a fresh ecc key pair
send("ecc", { // serialize into shippable values:
pubx: pair.pub.get().x,
puby: pair.pub.get().y,
sec: pair.sec.get()
});
}//end if ecc?
if(e.type == "encode") { // encode using ECC public key
try {
var pub=e.pub.split ? JSON.parse(e.pub) : e.pub,
curve = sjcl.ecc.curves.c521,
point = new sjcl.ecc.point(sjcl.ecc.curves.c521, curve.field.fromBits(pub.x), curve.field.fromBits(pub.y)),
pubK = new sjcl.ecc.elGamal.publicKey(sjcl.ecc.curves.c521, point),
plain = [].slice.call(new TextEncoder().encode(JSON.stringify(e.data))),
encrypted = sjcl.encrypt(pubK, plain);
send("encode", (""+encrypted));
} catch(y) {
send("error", y+"");
}
}//end if encode?
if(e.type == "decode") { // decode using ECC secret key
try {
var sec = new sjcl.ecc.elGamal.secretKey(
sjcl.ecc.curves.c521,
sjcl.ecc.curves.c521.field.fromBits( e.ob.sec )
),
decodedArray = sjcl.decrypt(sec, e.data, {raw: 1});
send("decode", new TextDecoder().decode(new Uint8Array(decodedArray)));
} catch(y) {
send("error", y+"");
}
}//end if decode?
if(e.type == "aesenc") { // encode using AES (GCM)
try {
var pw=sjcl.codec.hex.toBits(e.key);
var enc=sjcl.encrypt(pw, e.data, {mode : "gcm"});
send("enc", enc);
} catch(y) {
send("error", y.message);
}
}//end if encode?
if(e.type == "aesdec") {// decode using AES (GCM)
try {
var key=sjcl.codec.hex.toBits(e.key),
plain=sjcl.decrypt(key, e.data);
send("dec", plain);
} catch(y) {
send("error", y.message);
}
}//end if dec?
});//end onmessage() event handler
// utility functions
function stamp(){ // returns a bunch of random digits each time it's called
function random(n) {return [].slice.call(crypto.getRandomValues(new Uint32Array(n)));}
return [
Math.random(),
Math.floor(performance.now()*100),
(Date.now()-147298194451)/(location.href.match(/\d/g).length),
random(1)[0]
].join("").replace(/\D/g,"").replace(/^0+|0+$/g,"");
}
// crypto - OS-provided CSPRNG with timing data mixin - sync and fast
function getRandomFromCrypto(chars) {
var format="int";
chars=Math.floor(chars/1.5);
var pad=(""+Array(Math.max(64,Math.floor(chars/9.75)))).split("").map(stamp),
out = [].slice.call(crypto.getRandomValues(new Int16Array(chars)))
.map(Math.abs)
.filter(function(a,b,c){ return a<100; });
out = out.map(function(a,b,c){
var padSlot=Math.floor(b/10), padCol=b%10;
return ("00"+(a+(+pad[padSlot][padCol]||1)).toString().slice(-2)).slice(-2);
}).join("");
return out.slice(-chars);
} //end getRandomFromCrypto()
// a sync timestamp method, returns a new 10-digit string each time
function timeserial() {
return(Date.now() / (performance.now() * 100)).toString().split("").filter(/./.test, /\d/).slice(-10).join("");
};