Fully end to end encrypted anonymous chat program. Server only stores public key lookup for users and the encrypted messages. No credentials are transfered to the server, but kept in local browser storage. This allows 100% safe chatting.
https://safechat.ch
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
415 lines
14 KiB
415 lines
14 KiB
var password = null; // password, only stored temporary, until reload |
|
var username = null; // username, only used during registration |
|
var filecontent = new Array(); // temporary storage for attachments |
|
|
|
function error(data, stay) { |
|
$("#status").fadeOut("slow", function() { |
|
$("#status").addClass("error") |
|
$("#status").removeClass("notice") |
|
$("#status").removeClass("success") |
|
if (data) { |
|
if (typeof data == 'string') { |
|
$("#status").html(data); |
|
console.log("error: "+data); |
|
} else { |
|
$("#status").html('error'); |
|
console.log("error: "+JSON.stringify(data)); |
|
} |
|
} else { |
|
$("#status").html('error'); |
|
console.log("error"); |
|
} |
|
$("#status").fadeIn("slow"); |
|
if (!stay) setTimeout(start, 5000); |
|
}); |
|
} |
|
|
|
function notice(text) { |
|
$("#status").fadeOut("slow", function() { |
|
$("#status").addClass("notice") |
|
$("#status").removeClass("error") |
|
$("#status").removeClass("success") |
|
if (text) { |
|
$("#status").html(text); |
|
console.log("notice: "+text); |
|
} else { |
|
$("#status").html(''); |
|
console.log("notice"); |
|
} |
|
$("#status").fadeIn("slow"); |
|
}); |
|
} |
|
|
|
function success(text) { |
|
$("#status").fadeOut("slow", function() { |
|
$("#status").addClass("success") |
|
$("#status").removeClass("error") |
|
$("#status").removeClass("notice") |
|
if (text) { |
|
$("#status").html(text); |
|
console.log("success: "+text); |
|
} else { |
|
$("#status").html(''); |
|
console.log("success"); |
|
} |
|
$("#status").fadeIn("slow"); |
|
}); |
|
} |
|
|
|
function status(text, msg) { |
|
$("#main").fadeOut("slow", function() { |
|
$("#main").html(text); |
|
success(msg); |
|
$("#main").fadeIn("slow", function() { |
|
$("form input:first-child").focus(); |
|
}) |
|
}); |
|
} |
|
|
|
function checkuser(user) { |
|
$("#register").submit(function(event) { |
|
return false; |
|
}); |
|
$.post("checknewuser.php", {user: user}) |
|
.done(function(res) { |
|
username=JSON.parse(res); |
|
if (!username||username.length<1) username=null; |
|
$("#createuser").prop("disabled", !(username && password)); |
|
if (username) { |
|
if (password) success("user is ready to be created"); |
|
else notice("user name is available, please set password"); |
|
} else notice("user name is not available"); |
|
}).fail(function(res) { |
|
username=null; |
|
$("#createuser").prop("disabled", !(username && password)); |
|
error("offline"); |
|
}); |
|
} |
|
|
|
function checkpwd(pwd, pwd2) { |
|
$("#register").submit(function(event) { |
|
return false; |
|
}); |
|
if (pwd==pwd2) password=pwd; |
|
else password=null; |
|
if (!password||password.length<1) password=null; |
|
$("#createuser").prop("disabled", !(username && password)); |
|
if (password) { |
|
if (username) success("user is ready to be created"); |
|
else notice("password matches, please chose a valid user name"); |
|
} else notice("passwords don't match"); |
|
} |
|
|
|
function checkpartner(user) { |
|
$("#chat").submit(function(event) { |
|
return false; |
|
}); |
|
$.post("checknewuser.php", {user: user}) |
|
.done(function(res) { |
|
if (JSON.parse(res)) { |
|
notice("receiver does not exist"); |
|
$("#send").prop("disabled", true); |
|
return; |
|
} |
|
$("#send").prop("disabled", false); |
|
success("receiver exists"); |
|
}).fail(function(res) { |
|
error("offline", true); |
|
$("#send").prop("disabled", true); |
|
}); |
|
} |
|
|
|
function createNewUser(user, pwd) { |
|
status("generate keys"); |
|
openpgp.generateKeyPair({ |
|
numBits: 1024, |
|
userId: user, |
|
passphrase: pwd |
|
}).then(function(keyPair) { |
|
success("keys generated"); |
|
localStorage.pubKey = keyPair.publicKeyArmored; |
|
localStorage.privKey = keyPair.privateKeyArmored; |
|
login(); |
|
}).catch(function(e) { |
|
error(e); |
|
}); |
|
} |
|
|
|
function publicKey() { |
|
if (typeof localStorage.pubKey == 'undefined') return null; |
|
return openpgp.key.readArmored(localStorage.pubKey); |
|
} |
|
|
|
function privateKey() { |
|
if (typeof localStorage.privKey == 'undefined') return null; |
|
return openpgp.key.readArmored(localStorage.privKey); |
|
} |
|
|
|
function userid() { |
|
if (!publicKey() || |
|
publicKey().keys.length < 1 || |
|
publicKey().keys[0].getUserIds().length < 1) return null |
|
return publicKey().keys[0].getUserIds()[0]; |
|
} |
|
|
|
function clearmessage() { |
|
filecontent = new Array(); |
|
$('#preview').empty(); |
|
$("#msg").val(""); |
|
notice("message cleared"); |
|
} |
|
|
|
function attachments(files, id) { |
|
if (files) files.forEach(function(file) { |
|
if (file.content.length<100000) { |
|
var img = document.createElement('img'); |
|
img.src = file.content; |
|
$(id).append(img); |
|
} |
|
}); |
|
} |
|
|
|
function fileupload(evt) { |
|
if (!window.FileReader) return error("your browser dows not support file upload", true); |
|
for (var i=0, f; f=evt.target.files[i]; ++i) { |
|
var file = f; |
|
var reader = new FileReader(); |
|
reader.onload = function(evt) { |
|
if (evt.target.error) return error("error reading file", true); |
|
if (evt.target.readyState==0) return notice("waiting for data ..."); |
|
if (evt.target.readyState==1) return notice("loading data ..."); |
|
if (!file.type.match('^image/')) return error(file.name+": not an image", true); |
|
var img = document.createElement("img"); |
|
img.onload = function() { |
|
var MAX = 400; |
|
var width = img.width; |
|
var height = img.height; |
|
if (width > MAX) { |
|
height *= MAX / width; |
|
width = MAX; |
|
} |
|
if (height > MAX) { |
|
width *= MAX / height; |
|
height = MAX; |
|
} |
|
var canvas = document.createElement("canvas"); |
|
canvas.width = width; |
|
canvas.height = height; |
|
var ctx = canvas.getContext("2d"); |
|
ctx.drawImage(img, 0, 0, width, height); |
|
img.onload = function() { |
|
filecontent.push({type: file.type, content: img.src}); |
|
$("#preview").append(img); |
|
success('image of type '+file.type+' is ready to be sent'); |
|
} |
|
img.src = canvas.toDataURL(file.type); |
|
} |
|
img.src=evt.target.result; |
|
} |
|
reader.readAsDataURL(file); |
|
} |
|
} |
|
|
|
/* |
|
function fileupload(evt) { |
|
if (!window.FileReader) |
|
return error("your browser dows not support file upload", true); |
|
for (var i=0, f; f=evt.target.files[i]; ++i) { |
|
var file = f; |
|
var reader = new FileReader(); |
|
reader.onload = function(evt) { |
|
if (evt.target.error) return error("error reading file", true); |
|
if (evt.target.readyState==0) return notice("waiting for data ..."); |
|
if (evt.target.readyState==1) return notice("loading data ..."); |
|
var base64 = btoa(evt.target.result); |
|
filecontent.push({type: file.type, content: base64}); |
|
if (file.type.match('^image/')) { |
|
var img = document.createElement('img'); |
|
img.src = 'data:'+file.type+';base64,' + base64; |
|
$("#preview").append(img); |
|
success('image of type '+file.type+' is ready to be sent'); |
|
} else { |
|
success('file of type '+file.type+' is ready to be sent'); |
|
} |
|
} |
|
reader.readAsBinaryString(file); |
|
} |
|
} |
|
*/ |
|
|
|
function setreceiver(name) { |
|
$("#recv").val(name); |
|
checkpartner(name); |
|
$("#msg").focus(); |
|
} |
|
|
|
var startmsg = 0; // number of last downloaded message |
|
function get() { |
|
var beeped = false; |
|
$.post("get.php", {start: startmsg}) |
|
.done(function(res) { |
|
var msgs = JSON.parse(res); |
|
if (msgs) { |
|
msgs.forEach(function(e) { |
|
if (startmsg<Number(e.id)) startmsg = Number(e.id); |
|
$.post("pubkey.php", {user: e.user}) |
|
.done(function(pk) { |
|
var res=JSON.parse(pk); |
|
var key=openpgp.key.readArmored(res); |
|
if (!res||key.err) { |
|
setTimeout(get, 10000); |
|
return error("key of receiver not found", true); |
|
} |
|
var message = openpgp.message.readArmored(e.msg); |
|
var privkey = privateKey().keys[0]; |
|
if (privkey.decrypt(password)) |
|
openpgp.decryptAndVerifyMessage(privkey, key.keys, message) |
|
.then(function(msg) { |
|
var message = JSON.parse(msg.text); |
|
$("#msgs") // todo: check msg.signatures[0].valid |
|
.prepend('<div id="id'+(e.id)+'" class="msg '+ |
|
(e.user==userid()?"me":"other")+ |
|
'"><div class="header">'+ |
|
'<span class="date">'+ |
|
(new Date(1000*Number(e.time))).toLocaleString()+ |
|
'</span><span class="sender">'+ |
|
'<a href="javascript:void(0)" '+ |
|
'onclick="setreceiver(this.innerHTML)">'+ |
|
e.user+ |
|
'</a></span></div>'+ |
|
'<div class="text">'+ |
|
message.text+ |
|
'</div></div><div class="clear"/>'); |
|
attachments(message.files, '#id'+e.id+' .text'); |
|
$('#id'+e.id).emoticonize(); |
|
if (!beeped) |
|
(new Audio("A-Tone-His_Self-1266414414.mp3")) |
|
.play(); |
|
beeped = true; |
|
}) |
|
.catch(function(e) { |
|
// not for me |
|
}); |
|
}).fail(function(e) { |
|
error("offline", true); |
|
}); |
|
}); |
|
} |
|
}).fail(function(e) { |
|
error("offline", true) |
|
}); |
|
setTimeout(get, 10000); |
|
} |
|
|
|
function sendmessage(recv, txt) { |
|
notice("1/3 preparing message ..."); |
|
$("#message").fadeOut("slow"); |
|
$.post("pubkey.php", {user: recv}) |
|
.done(function(pk) { |
|
var res=JSON.parse(pk); |
|
var key=openpgp.key.readArmored(res); |
|
if (!res||key.err) { |
|
$("#message").fadeIn("slow"); |
|
error("key of receiver not found", true); |
|
return; |
|
} |
|
var privkey = privateKey().keys[0]; |
|
privkey.decrypt(password); |
|
var message = JSON.stringify({text: txt, files: filecontent}); |
|
notice("2/3 encrypting message ..."); |
|
openpgp.signAndEncryptMessage(key.keys.concat(publicKey().keys), privkey, message) |
|
.then(function(msg) { |
|
notice("3/3 sending message ..."); |
|
$.post("send.php", {user: userid(), msg: msg}) |
|
.done(function(res) { |
|
var st = JSON.parse(res); |
|
if (st.success) { |
|
$("#message").fadeIn("slow"); |
|
clearmessage(); |
|
success(st.txt); |
|
} else { |
|
$("#message").fadeIn("slow"); |
|
error(st.txt, true); |
|
} |
|
}) |
|
.fail(function() { |
|
error("offline", true); |
|
}); |
|
}) |
|
.catch(function(e) { |
|
$("#message").fadeIn("slow"); |
|
error("encryption of message failed", true); |
|
}); |
|
}) |
|
.fail(function(e) { |
|
$("#message").fadeIn("slow"); |
|
error("offline", true); |
|
}); |
|
$("#message").fadeIn("slow"); |
|
} |
|
|
|
function setpw(pwd) { |
|
if (privateKey().keys[0].decrypt(pwd)) { |
|
password = pwd; |
|
chat(); |
|
} |
|
} |
|
|
|
function getpwd() { |
|
status('<form>'+ |
|
' <input placeholder="password for '+userid()+ |
|
'" id="pwd" oninput="setpw(this.value)" type="password" />'+ |
|
'</form>'); |
|
} |
|
|
|
function chat() { |
|
if (!password) return getpwd(); |
|
$.ajax({url: "chat.html", success: function(res) { |
|
status(res); |
|
setTimeout(get, 2000); |
|
}}).fail(function() { |
|
error("offline") |
|
}); |
|
} |
|
|
|
function login() { |
|
status("login ..."); |
|
$.post("login.php", {user: userid(), |
|
pubkey: localStorage.pubKey}, |
|
function(res) { |
|
var st = JSON.parse(res); |
|
if (st.success) { |
|
status("logged in ...", st.txt); |
|
chat(); |
|
} else { |
|
error(st.txt); |
|
} |
|
}) |
|
.fail(function(e) { |
|
error("offline"); |
|
}); |
|
} |
|
|
|
function newuser() { |
|
status("new user ..."); |
|
$.ajax({url: "newuser.html", success: function(res) { |
|
status(res); |
|
}}).fail(function() { |
|
error("offline"); |
|
}); |
|
} |
|
|
|
function start() { |
|
try { |
|
status("Starting up ..."); |
|
if (!userid()) { |
|
newuser(); |
|
} else { |
|
login(); |
|
} |
|
} catch (m) { |
|
error(m); |
|
} |
|
} |
|
|
|
$(start);
|
|
|