in the middle of rewriting it

master
Marc Wäckerlin 8 years ago
parent d0e32dfcae
commit ded3085d90
  1. 1
      nodejs/database/index.js
  2. 9
      nodejs/etc/systemd/system/safechat.service
  3. 117
      nodejs/public/javascripts/safechat.js
  4. 30
      nodejs/routes/index.js
  5. 21
      nodejs/safechat.js
  6. 1
      nodejs/sockets/index.js
  7. 21
      nodejs/views/index.ejs

@ -3,7 +3,6 @@ module.exports = function(config) {
var fs = require('fs'); var fs = require('fs');
config.multipleStatements = true; config.multipleStatements = true;
var pool = mysql.createPool(config); var pool = mysql.createPool(config);
console.log(__dirname+'/schema.sql')
pool.query(fs.readFileSync(__dirname+'/schema.sql').toString()); pool.query(fs.readFileSync(__dirname+'/schema.sql').toString());
if (config.max_allowed_packet) if (config.max_allowed_packet)
pool.query("set global max_allowed_packet=?", [config.max_allowed_packet]); pool.query("set global max_allowed_packet=?", [config.max_allowed_packet]);

@ -0,0 +1,9 @@
[Unit]
Description=Secure and Encrypted Chat Server
[Service]
ExecStart=/usr/bin/nodejs /usr/share/safechat/nodejs/safechat > /var/log/safechat.log
Restart=on-abort
[Install]
WantedBy=multi-user.target

@ -20,8 +20,8 @@
start -> newuser [label="if no keys exist"]; start -> newuser [label="if no keys exist"];
start -> login [label="if keys exist"]; start -> login [label="if keys exist"];
newuser -> createkeypair [label="on submit"]; newuser -> createkeypair [label="on submit"];
createkeypair -> "openpgp.generateKeyPair"; createkeypair -> "openpgp.generateKey";
"openpgp.generateKeyPair" -> login [label="keys generated in local store"]; "openpgp.generateKey" -> login [label="keys generated in local store"];
login -> chat [label="user is valid on server"]; login -> chat [label="user is valid on server"];
chat -> getpwd [label="password not yet entered"]; chat -> getpwd [label="password not yet entered"];
getpwd -> setpw [label="on input"]; getpwd -> setpw [label="on input"];
@ -33,20 +33,88 @@
sendmessage -> chat [label="remain in chat"]; sendmessage -> chat [label="remain in chat"];
} }
@enddot @enddot
*/ */
// 1 2 3 4 5 6 7 8 // 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890 // 45678901234567890123456789012345678901234567890123456789012345678901234567890
function SafeChatClient(success, notice, error) {
/// Cache Client's Key from local Strorage
var k = null;
/// Get User Key
/** @internal key ist cached in k
@return key */
function key() {
if (k) return k
if (typeof localStorage.key == 'undefined') return null
return k = openpgp.key.readArmored(localStorage.key)
}
/// Get Own User Name
/** Get user name as user id of first public key */
function uid() {
if (k || key()) return k.pub.keys[0].getUserIds()[0]
return null
}
/// Create New User
function createuser(user, email, pwd) {
notice("generating keys")
openpgp.generateKey({
numBits: 4096,
userIds: [{name: user, email: email}],
passphrase: pwd
}).then(function(keyPair) {
success("keys generated")
localStorage.key = keyPair.privateKeyArmored
k = keyPair.key
}).catch(function(e) {
console.log(e)
error("generating key pairs failed")
})
}
function password(pwd) {
return (k || keys()) && k.keys[0].decrypt(pwd)
}
/// Encrypt Message
function encrypt(targetkeys, message, done, failed) {
if (!k) return false
openpgp.encrypt({
publicKeys: targets.keys.concat(k.keys),
privateKeys: k,
data: message,
armor: false})
.then(done)
.else(failed)
return true
}
/// Decrypt Message
function decrypt(message) {
if (!k) return false
return true
}
}
var password = null; ///< password, only stored temporary, until reload var password = null; ///< password, only stored temporary, until reload
var username = null; ///< username, only used during registration var username = null; ///< username, only used during registration
var filecontent = new Array(); ///< temporary storage for attachments var filecontent = new Array(); ///< temporary storage for attachments
var socket = io.connect(); var socket = io.connect();
var hostname = window.location.hostname!='localhost'?window.location.hostname:'safechat.ch';
/// Padding for numbers in dates /// Padding for numbers in dates
function pad(n) { function pad(n) {
return n<10 ? '0'+n : n return n<10 ? '0'+n : n
} }
function uid(name) {
return name+' <'+name+'@'+hostname+'>';
}
/// Convert number of bytes to readable text /// Convert number of bytes to readable text
function size(num) { function size(num) {
if (num>0.6*1024) { if (num>0.6*1024) {
@ -206,7 +274,7 @@ function backup() {
var now = new Date(); var now = new Date();
download.download = download.download =
pad(now.getFullYear())+pad(now.getMonth()+1)+pad(now.getDate())+ pad(now.getFullYear())+pad(now.getMonth()+1)+pad(now.getDate())+
"-"+userid()+"@"+window.location.hostname+".bak"; "-"+userid()+"@"+hostname+".bak";
var clickEvent = new MouseEvent("click", { var clickEvent = new MouseEvent("click", {
"view": window, "view": window,
"bubbles": true, "bubbles": true,
@ -284,16 +352,16 @@ function checkpartner(user) {
$("#chat").submit(function(event) { $("#chat").submit(function(event) {
return false; return false;
}); });
emit("user", user); emit("user", uid(user));
} }
/// Create Local Public-/Private-Key Pair /// Create Local Public-/Private-Key Pair
/** Called if user has not yet his keys, just generates a new key pair. */ /** Called if user has not yet his keys, just generates a new key pair. */
function createkeypair(user, pwd) { function createkeypair(user, pwd) {
notice("generating keys"); notice("generating keys");
openpgp.generateKeyPair({ openpgp.generateKey({
numBits: 4096, numBits: 4096,
userId: user, userIds: [{name: user, email: user+'@'+hostname}],
passphrase: pwd passphrase: pwd
}).then(function(keyPair) { }).then(function(keyPair) {
success("keys generated"); success("keys generated");
@ -301,7 +369,8 @@ function createkeypair(user, pwd) {
localStorage.privkey = keyPair.privateKeyArmored; localStorage.privkey = keyPair.privateKeyArmored;
login(); login();
}).catch(function(e) { }).catch(function(e) {
error("generating key pairs failed"); console.log(e)
error("generating key pairs failed")
}); });
} }
@ -357,7 +426,7 @@ function guessfilename(mimetype, user, date) {
if (!date) date = new Date(); if (!date) date = new Date();
var ext = mimetype.replace(/.*\/(x-)?/i, ""); var ext = mimetype.replace(/.*\/(x-)?/i, "");
return pad(date.getFullYear())+pad(date.getMonth()+1)+pad(date.getDate()) return pad(date.getFullYear())+pad(date.getMonth()+1)+pad(date.getDate())
+"-"+ext+"-"+user+"@"+window.location.hostname+'.'+ext; +"-"+ext+"-"+user+"@"+hostname+'.'+ext;
} }
/// Display Image Attachments /// Display Image Attachments
@ -545,7 +614,7 @@ function loggedin() {
function user(usr) { function user(usr) {
if (usr.exists) console.log("rcv-> user("+usr.name+")"); if (usr.exists) console.log("rcv-> user("+usr.name+")");
else console.log("rcv-> user("+usr.name+"): name is available"); else console.log("rcv-> user("+usr.name+"): name is available");
if ($("#newuser").is(":visible") && usr.name==$('#user').val()) { if ($("#newuser").is(":visible") && usr.name==uid($('#user').val())) {
// same username as in the create user form // same username as in the create user form
$("#createuser").prop("disabled", usr.exists); // todo: check password $("#createuser").prop("disabled", usr.exists); // todo: check password
if (!usr.exists) { if (!usr.exists) {
@ -556,7 +625,7 @@ function user(usr) {
error("user name "+usr.name+" is in use", true); error("user name "+usr.name+" is in use", true);
} }
} }
if ($("#chat").is(":visible") && usr.name==$("#recv").val()) { // same username as in receiver if ($("#chat").is(":visible") && usr.name==uid($("#recv").val())) { // same username as in receiver
$('#send').prop("disabled", !usr.exists); $('#send').prop("disabled", !usr.exists);
$("label[for=send] img").css("opacity", usr.exists?"1.0":"0.4"); $("label[for=send] img").css("opacity", usr.exists?"1.0":"0.4");
$("label[for=send] img").css("filter", usr.exists?"alpha(opacity=100)":"alpha(opacity=40)"); $("label[for=send] img").css("filter", usr.exists?"alpha(opacity=100)":"alpha(opacity=40)");
@ -577,6 +646,11 @@ function user(usr) {
} }
} }
function queryuser(usr) {
console.log("query user: "+uid(usr));
socket.emit("user", uid(usr));
}
/// Get a user's public key. /// Get a user's public key.
/** The first time, gets it from the server, later from the cache. */ /** The first time, gets it from the server, later from the cache. */
function getPublicKey(user) { function getPublicKey(user) {
@ -606,8 +680,11 @@ function message(m, internal) {
var message = openpgp.message.readArmored(m.msg); var message = openpgp.message.readArmored(m.msg);
var privkey = privateKey().keys[0]; var privkey = privateKey().keys[0];
if (privkey.decrypt(password)) // prepare own key if (privkey.decrypt(password)) // prepare own key
openpgp.decryptAndVerifyMessage(privkey, key.keys, message) openpgp.decrypt({
.then(function(msg) { // decryption succeded privateKeys: privkey,
publicKeys: key.keys,
message: message
}).then(function(msg) { // decryption succeded
// prepend message to list of messages // prepend message to list of messages
var message = JSON.parse(msg.text); var message = JSON.parse(msg.text);
$("#msgs") // todo: check msg.signatures[0].valid $("#msgs") // todo: check msg.signatures[0].valid
@ -632,8 +709,7 @@ function message(m, internal) {
// calculate and show emoticons // calculate and show emoticons
$('#id'+m.id).emoticonize(); $('#id'+m.id).emoticonize();
if (!internal) beep(m.user); if (!internal) beep(m.user);
}) }).catch(function(e) {
.catch(function(e) {
// not for me // not for me
success(); success();
}); });
@ -657,9 +733,10 @@ function sendmessage(recv, txt) {
privkey.decrypt(password); // get own private key ready privkey.decrypt(password); // get own private key ready
var message = JSON.stringify({receiver: recv, text: txt, files: filecontent}); var message = JSON.stringify({receiver: recv, text: txt, files: filecontent});
notice("2/3 encrypting message …"); notice("2/3 encrypting message …");
openpgp.signAndEncryptMessage(key.keys.concat(publicKey().keys), openpgp.encrypt({publicKeys: key.keys.concat(publicKey().keys),
privkey, privateKeys: privkey,
message) data: message,
armor: false})
.then(function(msg) { // message is encrypted .then(function(msg) { // message is encrypted
notice("3/3 sending message …"); notice("3/3 sending message …");
emit("message", {user: userid(), content: msg}); emit("message", {user: userid(), content: msg});
@ -743,7 +820,7 @@ function chat() {
different, then this is a complete failure, something went different, then this is a complete failure, something went
terribly wrong. */ terribly wrong. */
function login() { function login() {
$("#username").html(userid()+"@"+window.location.hostname); $("#username").html(userid()+"@"+hostname);
emit("login", {name: userid(), emit("login", {name: userid(),
pubkey: localStorage.pubkey}); pubkey: localStorage.pubkey});
success("login sent to server"); success("login sent to server");
@ -810,7 +887,7 @@ function init() {
socket.on("message", message); socket.on("message", message);
socket.on("messages", messages); socket.on("messages", messages);
connectionstatus(); connectionstatus();
if (openpgp.initWorker("javascripts/openpgp.worker.js")) if (openpgp.initWorker("openpgp.worker.min.js"))
console.log("asynchronous openpgp enabled"); console.log("asynchronous openpgp enabled");
else else
console.log("asynchronous openpgp failed"); console.log("asynchronous openpgp failed");

@ -1,22 +1,14 @@
module.exports = function(app, package) {
/*
* GET home page. [
*/ '',
'webrtc'
var package = require(__dirname+"/../package.json"); ].forEach(function(p) {
app.get('/'+p, function(req, res) {
exports.index = function(req, res) { res.render(p?p:'index', {
res.render(path, { package: package
projecturl: package.documentation, })
packagename: package.name, })
packageversion: package.version
}) })
}
exports.webrtc = function(req, res) {
res.render('webrtc', {
projecturl: package.documentation,
packagename: package.name,
packageversion: package.version
});
} }

@ -5,8 +5,8 @@
var package = require(__dirname+'/package.json'); var package = require(__dirname+'/package.json');
var config = require(package.path.config); var config = require(package.path.config);
var express = require('express'); var express = require('express');
var routes = require(__dirname+'/routes');
var app = module.exports = express.createServer(); var app = module.exports = express.createServer();
var routes = require(__dirname+'/routes')(app, package);
var io = require('socket.io').listen(app); var io = require('socket.io').listen(app);
var sql = require(__dirname+'/database')(config.mysql); var sql = require(__dirname+'/database')(config.mysql);
var sockets = require(__dirname+'/sockets')(sql); var sockets = require(__dirname+'/sockets')(sql);
@ -34,11 +34,17 @@ app.configure(function() {
app.use(app.router); app.use(app.router);
app.use(express.static(__dirname + '/public')); app.use(express.static(__dirname + '/public'));
[ [
'jquery/dist', 'jquery/dist/jquery.min.js',
'openpgp/dist' 'jquery/dist/jquery.js',
'openpgp/dist/openpgp.min.js',
'openpgp/dist/openpgp.js',
'openpgp/dist/openpgp.worker.min.js',
'openpgp/dist/openpgp.worker.js'
].forEach(function(file) { ].forEach(function(file) {
app.use('/'+file.replace(/\/.*/g, ''), app.get('/'+file.replace(/.*\//g, ''),
express.static(__dirname + '/node_modules/'+file)); function(req, res) {
res.sendfile('/node_modules/'+file, {root: __dirname})
})
}) })
}); });
@ -54,11 +60,6 @@ app.configure('production', function(){
io.sockets.on('connection', sockets.connection); io.sockets.on('connection', sockets.connection);
// Routes
app.get('/', routes.index);
app.get('/webrtc', routes.webrtc);
app.listen(config.port, function(){ app.listen(config.port, function(){
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env); console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
}); });

@ -56,6 +56,7 @@ module.exports = function(sql) {
}); });
socket.on("login", function(user) { socket.on("login", function(user) {
console.log('-> signal: login('+user.name+')');
if (!user.name || !user.pubkey) return emit("fail", "wrong login format"); if (!user.name || !user.pubkey) return emit("fail", "wrong login format");
console.log("-> signal: login("+user.name+")"); console.log("-> signal: login("+user.name+")");
if (user.name=="safechat") return emit("fail", "user name safechat is reserved"); if (user.name=="safechat") return emit("fail", "user name safechat is reserved");

@ -4,9 +4,8 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width initial-scale=1" /> <meta name="viewport" content="width=device-width initial-scale=1" />
<link href="stylesheets/safechat.css" rel="stylesheet" type="text/css" /> <link href="stylesheets/safechat.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="jquery/jquery.min.js"></script> <script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="openpgp/openpgp.min.js"></script> <script type="text/javascript" src="openpgp.min.js"></script>
<script type="text/javascript" src="openpgp/openpgp.worker.min.js"></script>
<script type="text/javascript" src="socket.io/socket.io.js"></script> <script type="text/javascript" src="socket.io/socket.io.js"></script>
<script type="text/javascript" src="javascripts/mediarecorder.js"></script> <script type="text/javascript" src="javascripts/mediarecorder.js"></script>
<script type="text/javascript" src="javascripts/safechat.js"></script> <script type="text/javascript" src="javascripts/safechat.js"></script>
@ -18,7 +17,7 @@
<body> <body>
<div id="header" class="header"> <div id="header" class="header">
<h1>Safe Chat <%= packageversion %></h1> <h1>Safe Chat <%= package.version %></h1>
<div id="togglemenu"> <div id="togglemenu">
<span id="username">[unknown]</span> <span id="username">[unknown]</span>
<span id="connectionstatus"> <span id="connectionstatus">
@ -35,7 +34,7 @@
<li id="groups" onclick="groups()">Edit Groups</li> <li id="groups" onclick="groups()">Edit Groups</li>
<li id="removeKey" style="display: none" onclick="removeKey()">Password Forgotten</li> <li id="removeKey" style="display: none" onclick="removeKey()">Password Forgotten</li>
<li id="android-download" href="safechat.apk"><a href="safechat.apk">Download Android-App</a></li> <li id="android-download" href="safechat.apk"><a href="safechat.apk">Download Android-App</a></li>
<li href="<%= projecturl %>" target="_blank"><a href="<%= projecturl %>" target="_blank">About Safe Chat</a></li> <li href="<%= package.documentation %>" target="_blank"><a href="<%= package.documentation %>" target="_blank">About Safe Chat</a></li>
</ul> </ul>
<script type="text/javascript"> <script type="text/javascript">
$(function() { // on load: without cordova, remove andoid-download $(function() { // on load: without cordova, remove andoid-download
@ -56,16 +55,14 @@
<input placeholder="username" autocomplete="off" type="text" id="user"/> <input placeholder="username" autocomplete="off" type="text" id="user"/>
<input placeholder="password" autocomplete="off" type="password" id="pwd" oninput="checkpwd(this.value, document.getElementById('pwd2').value)"/> <input placeholder="password" autocomplete="off" type="password" id="pwd" oninput="checkpwd(this.value, document.getElementById('pwd2').value)"/>
<input placeholder="repeat password" autocomplete="off" type="password" id="pwd2" oninput="checkpwd(document.getElementById('pwd').value, this.value)"/> <input placeholder="repeat password" autocomplete="off" type="password" id="pwd2" oninput="checkpwd(document.getElementById('pwd').value, this.value)"/>
<input id="createuser" type="submit" disabled/> <button id="createuser" disabled>register</button>
</form> </form>
<script> <script>
$("#user").on("input", function() { $("#user").on("input", function() {
console.log("query user: "+$('#user').val()); queryuser($('#user').val());
socket.emit("user", $('#user').val());
}); });
$("#register").submit(function(event) { $("#createuser").on("click", function(event) {
createkeypair(event.target.elements['user'].value, createkeypair($('#user').val(), $('#pwd').val());
event.target.elements['pwd'].value);
return false; return false;
}); });
</script> </script>
@ -193,7 +190,7 @@
server. Your password and your secret key are fully under server. Your password and your secret key are fully under
your control. That's why you must enable javascript and your control. That's why you must enable javascript and
local storage for this application.</p> local storage for this application.</p>
<p><a href="<%= projecturl %>" target="_blank">more information</a></p> <p><a href="<%= package.documentation %>" target="_blank">more information</a></p>
</noscript> </noscript>
<!-- Error: Missing LocalStorage --> <!-- Error: Missing LocalStorage -->

Loading…
Cancel
Save