Bugfix. Updates #111

When using OTR with prebind, the user password isn't defined.
=============================================================

When not using prebind, the user password is used to encrypt the private key
for the OTR session before it's saved in session storage.

When using prebind, we ideally want to use the same OTR private key across page
loads, so that we don't have to spend the time generating a new one together
with AKE on every page load. To do this, we need to store it somewhere, like
the browser's session storage.

However, I have yet to find a secure way to store the OTR private key that does
not expose it to maliciously injected javascript.

For now, I've updated the code to generate a new private key and do the AKE
with every page reload.

I'm considering adding code to store the private key in Session Storage and
letting the user explicitly enable this (while making them aware of the risks
involved).
This commit is contained in:
JC Brand 2014-01-31 05:50:38 +02:00
parent 9bce25109c
commit 8232cdaff2

View File

@ -451,20 +451,7 @@
this.rosterview = new this.RosterView({'model':this.roster}); this.rosterview = new this.RosterView({'model':this.roster});
}; };
this.onConnected = function () { this.registerGlobalEventHandlers = function () {
if (this.debug) {
this.connection.xmlInput = function (body) { console.log(body); };
this.connection.xmlOutput = function (body) { console.log(body); };
Strophe.log = function (level, msg) { console.log(level+' '+msg); };
Strophe.error = function (msg) { console.log('ERROR: '+msg); };
}
this.bare_jid = Strophe.getBareJidFromJid(this.connection.jid);
this.domain = Strophe.getDomainFromJid(this.connection.jid);
this.features = new this.Features();
this.initStatus($.proxy(function () {
this.initRoster();
this.chatboxes.onConnected();
this.connection.roster.get(function () {});
$(document).click(function() { $(document).click(function() {
if ($('.toggle-otr ul').is(':visible')) { if ($('.toggle-otr ul').is(':visible')) {
$('.toggle-otr ul', this).slideUp(); $('.toggle-otr ul', this).slideUp();
@ -480,6 +467,23 @@
} }
this.windowState = e.type; this.windowState = e.type;
},this)); },this));
};
this.onConnected = function () {
if (this.debug) {
this.connection.xmlInput = function (body) { console.log(body); };
this.connection.xmlOutput = function (body) { console.log(body); };
Strophe.log = function (level, msg) { console.log(level+' '+msg); };
Strophe.error = function (msg) { console.log('ERROR: '+msg); };
}
this.bare_jid = Strophe.getBareJidFromJid(this.connection.jid);
this.domain = Strophe.getDomainFromJid(this.connection.jid);
this.features = new this.Features();
this.initStatus($.proxy(function () {
this.initRoster();
this.chatboxes.onConnected();
this.connection.roster.get(function () {});
this.registerGlobalEventHandlers();
this.giveFeedback(__('Online Contacts')); this.giveFeedback(__('Online Contacts'));
if (this.callback) { if (this.callback) {
@ -513,9 +517,6 @@
this.ChatBox = Backbone.Model.extend({ this.ChatBox = Backbone.Model.extend({
initialize: function () { initialize: function () {
if (this.get('box_id') !== 'controlbox') { if (this.get('box_id') !== 'controlbox') {
if (_.contains([UNVERIFIED, VERIFIED], this.get('otr_status'))) {
this.initiateOTR();
}
this.messages = new converse.Messages(); this.messages = new converse.Messages();
this.messages.localStorage = new Backbone.LocalStorage( this.messages.localStorage = new Backbone.LocalStorage(
hex_sha1('converse.messages'+this.get('jid')+converse.bare_jid)); hex_sha1('converse.messages'+this.get('jid')+converse.bare_jid));
@ -527,43 +528,53 @@
} }
}, },
getSession: function () { getSession: function (callback) {
// XXX: sessionStorage is not supported in IE < 8. Perhaps a // FIXME: sessionStorage is not supported in IE < 8. Perhaps a
// user alert is required here... // user alert is required here...
var saved_key = window.sessionStorage[hex_sha1(this.id+'priv_key')]; var result, pass, instance_tag, saved_key;
var instance_tag = window.sessionStorage[hex_sha1(this.id+'instance_tag')];
var cipher = CryptoJS.lib.PasswordBasedCipher; var cipher = CryptoJS.lib.PasswordBasedCipher;
var pass = converse.connection.pass; if (typeof converse.connection.pass !== "undefined") {
instance_tag = window.sessionStorage[hex_sha1(this.id+'instance_tag')];
saved_key = window.sessionStorage[hex_sha1(this.id+'priv_key')];
pass = converse.connection.pass;
var pass_check = this.get('pass_check'); var pass_check = this.get('pass_check');
var result, key;
if (saved_key && instance_tag && typeof pass_check !== 'undefined') { if (saved_key && instance_tag && typeof pass_check !== 'undefined') {
var decrypted = cipher.decrypt(CryptoJS.algo.AES, saved_key, pass); var decrypted = cipher.decrypt(CryptoJS.algo.AES, saved_key, pass);
key = DSA.parsePrivate(decrypted.toString(CryptoJS.enc.Latin1)); var key = DSA.parsePrivate(decrypted.toString(CryptoJS.enc.Latin1));
if (cipher.decrypt(CryptoJS.algo.AES, pass_check, pass).toString(CryptoJS.enc.Latin1) === 'match') { if (cipher.decrypt(CryptoJS.algo.AES, pass_check, pass).toString(CryptoJS.enc.Latin1) === 'match') {
// Verified that the user's password is still the same // Verified that the user's password is still the same
this.trigger('showHelpMessages', [__('Re-establishing encrypted session')]); this.trigger('showHelpMessages', [__('Re-establishing encrypted session')]);
return { callback({
'key': key, 'key': key,
'instance_tag': instance_tag 'instance_tag': instance_tag
}; });
} }
} }
}
// We need to generate a new key and instance tag // We need to generate a new key and instance tag
result = alert(__('Your browser needs to generate a private key, which will be used in your encrypted chat session. This can take up to 30 seconds during which your browser might freeze and become unresponsive.'));
instance_tag = OTR.makeInstanceTag(); instance_tag = OTR.makeInstanceTag();
key = new DSA(); this.trigger('showHelpMessages', [__('Generating private key.')]);
this.trigger('showHelpMessages', [__('Your browser might become unresponsive.')]);
var clb = callback;
setTimeout($.proxy(function () {
var key = new DSA();
if (typeof converse.connection.pass !== "undefined") {
// Encrypt the key and set in sessionStorage. Also store // Encrypt the key and set in sessionStorage. Also store
// instance tag // instance tag
window.sessionStorage[hex_sha1(this.id+'priv_key')] = window.sessionStorage[hex_sha1(this.id+'priv_key')] =
cipher.encrypt(CryptoJS.algo.AES, key.packPrivate(), pass).toString(); cipher.encrypt(CryptoJS.algo.AES, key.packPrivate(), pass).toString();
window.sessionStorage[hex_sha1(this.id+'instance_tag')] = instance_tag; window.sessionStorage[hex_sha1(this.id+'instance_tag')] = instance_tag;
this.trigger('showHelpMessages', [__('Private key generated.')]);
this.save({'pass_check': cipher.encrypt(CryptoJS.algo.AES, 'match', pass).toString()}); this.save({'pass_check': cipher.encrypt(CryptoJS.algo.AES, 'match', pass).toString()});
return { }
this.trigger('showHelpMessages', [__('Private key generated.')]);
clb({
'key': key, 'key': key,
'instance_tag': instance_tag 'instance_tag': instance_tag
}; });
}, this), 500);
}, },
updateOTRStatus: function (state) { updateOTRStatus: function (state) {
@ -616,7 +627,7 @@
// query message from our buddy. Otherwise, it is us who will // query message from our buddy. Otherwise, it is us who will
// send the query message to them. // send the query message to them.
this.save({'otr_status': UNENCRYPTED}); this.save({'otr_status': UNENCRYPTED});
var session = this.getSession(); var session = this.getSession($.proxy(function (session) {
this.otr = new OTR({ this.otr = new OTR({
fragment_size: 140, fragment_size: 140,
send_interval: 200, send_interval: 200,
@ -637,11 +648,13 @@
this.trigger('showOTRError', msg); this.trigger('showOTRError', msg);
}, this)); }, this));
this.trigger('showHelpMessages', [__('Exchanging private key with buddy.')]);
if (query_msg) { if (query_msg) {
this.otr.receiveMsg(query_msg); this.otr.receiveMsg(query_msg);
} else { } else {
this.otr.sendQueryMsg(); this.otr.sendQueryMsg();
} }
}, this));
}, },
endOTR: function () { endOTR: function () {
@ -700,7 +713,11 @@
return this.createMessage(message); return this.createMessage(message);
} }
if (_.contains([UNVERIFIED, VERIFIED], this.get('otr_status'))) { if (_.contains([UNVERIFIED, VERIFIED], this.get('otr_status'))) {
if (text.match(/^\?OTRv23?/)) {
this.initiateOTR(text);
} else {
this.otr.receiveMsg(text); this.otr.receiveMsg(text);
}
} else { } else {
if (text.match(/^\?OTR/)) { if (text.match(/^\?OTR/)) {
// They want to initiate OTR // They want to initiate OTR
@ -849,6 +866,10 @@
if (this.model.get('status')) { if (this.model.get('status')) {
this.showStatusMessage(this.model.get('status')); this.showStatusMessage(this.model.get('status'));
} }
if (_.contains([UNVERIFIED, VERIFIED], this.model.get('otr_status'))) {
this.model.initiateOTR();
}
}, },
render: function () { render: function () {