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,6 +451,24 @@
this.rosterview = new this.RosterView({'model':this.roster}); this.rosterview = new this.RosterView({'model':this.roster});
}; };
this.registerGlobalEventHandlers = function () {
$(document).click(function() {
if ($('.toggle-otr ul').is(':visible')) {
$('.toggle-otr ul', this).slideUp();
}
if ($('.toggle-smiley ul').is(':visible')) {
$('.toggle-smiley ul', this).slideUp();
}
});
$(window).on("blur focus", $.proxy(function(e) {
if ((this.windowState != e.type) && (e.type == 'focus')) {
converse.clearMsgCounter();
}
this.windowState = e.type;
},this));
};
this.onConnected = function () { this.onConnected = function () {
if (this.debug) { if (this.debug) {
this.connection.xmlInput = function (body) { console.log(body); }; this.connection.xmlInput = function (body) { console.log(body); };
@ -465,21 +483,7 @@
this.initRoster(); this.initRoster();
this.chatboxes.onConnected(); this.chatboxes.onConnected();
this.connection.roster.get(function () {}); this.connection.roster.get(function () {});
$(document).click(function() { this.registerGlobalEventHandlers();
if ($('.toggle-otr ul').is(':visible')) {
$('.toggle-otr ul', this).slideUp();
}
if ($('.toggle-smiley ul').is(':visible')) {
$('.toggle-smiley ul', this).slideUp();
}
});
$(window).on("blur focus", $.proxy(function(e) {
if ((this.windowState != e.type) && (e.type == 'focus')) {
converse.clearMsgCounter();
}
this.windowState = e.type;
},this));
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") {
var pass_check = this.get('pass_check'); instance_tag = window.sessionStorage[hex_sha1(this.id+'instance_tag')];
var result, key; saved_key = window.sessionStorage[hex_sha1(this.id+'priv_key')];
if (saved_key && instance_tag && typeof pass_check !== 'undefined') { pass = converse.connection.pass;
var decrypted = cipher.decrypt(CryptoJS.algo.AES, saved_key, pass); var pass_check = this.get('pass_check');
key = DSA.parsePrivate(decrypted.toString(CryptoJS.enc.Latin1)); if (saved_key && instance_tag && typeof pass_check !== 'undefined') {
if (cipher.decrypt(CryptoJS.algo.AES, pass_check, pass).toString(CryptoJS.enc.Latin1) === 'match') { var decrypted = cipher.decrypt(CryptoJS.algo.AES, saved_key, pass);
// Verified that the user's password is still the same var key = DSA.parsePrivate(decrypted.toString(CryptoJS.enc.Latin1));
this.trigger('showHelpMessages', [__('Re-establishing encrypted session')]); if (cipher.decrypt(CryptoJS.algo.AES, pass_check, pass).toString(CryptoJS.enc.Latin1) === 'match') {
return { // Verified that the user's password is still the same
'key': key, this.trigger('showHelpMessages', [__('Re-establishing encrypted session')]);
'instance_tag': instance_tag callback({
}; 'key': key,
'instance_tag': 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();
key = new DSA();
// Encrypt the key and set in sessionStorage. Also store
// instance tag
window.sessionStorage[hex_sha1(this.id+'priv_key')] =
cipher.encrypt(CryptoJS.algo.AES, key.packPrivate(), pass).toString();
window.sessionStorage[hex_sha1(this.id+'instance_tag')] = instance_tag;
this.trigger('showHelpMessages', [__('Private key generated.')]); // We need to generate a new key and instance tag
this.save({'pass_check': cipher.encrypt(CryptoJS.algo.AES, 'match', pass).toString()}); instance_tag = OTR.makeInstanceTag();
return { this.trigger('showHelpMessages', [__('Generating private key.')]);
'key': key, this.trigger('showHelpMessages', [__('Your browser might become unresponsive.')]);
'instance_tag': instance_tag
}; 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
// instance tag
window.sessionStorage[hex_sha1(this.id+'priv_key')] =
cipher.encrypt(CryptoJS.algo.AES, key.packPrivate(), pass).toString();
window.sessionStorage[hex_sha1(this.id+'instance_tag')] = instance_tag;
this.save({'pass_check': cipher.encrypt(CryptoJS.algo.AES, 'match', pass).toString()});
}
this.trigger('showHelpMessages', [__('Private key generated.')]);
clb({
'key': key,
'instance_tag': instance_tag
});
}, this), 500);
}, },
updateOTRStatus: function (state) { updateOTRStatus: function (state) {
@ -616,32 +627,34 @@
// 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,
priv: session.key, priv: session.key,
instance_tag: session.instance_tag, instance_tag: session.instance_tag,
debug: this.debug debug: this.debug
}); });
this.otr.on('status', $.proxy(this.updateOTRStatus, this)); this.otr.on('status', $.proxy(this.updateOTRStatus, this));
this.otr.on('smp', $.proxy(this.onSMP, this)); this.otr.on('smp', $.proxy(this.onSMP, this));
this.otr.on('ui', $.proxy(function (msg) { this.otr.on('ui', $.proxy(function (msg) {
this.trigger('showReceivedOTRMessage', msg); this.trigger('showReceivedOTRMessage', msg);
}, this)); }, this));
this.otr.on('io', $.proxy(function (msg) { this.otr.on('io', $.proxy(function (msg) {
this.trigger('sendMessageStanza', msg); this.trigger('sendMessageStanza', msg);
}, this)); }, this));
this.otr.on('error', $.proxy(function (msg) { this.otr.on('error', $.proxy(function (msg) {
this.trigger('showOTRError', msg); this.trigger('showOTRError', msg);
}, this)); }, this));
if (query_msg) { this.trigger('showHelpMessages', [__('Exchanging private key with buddy.')]);
this.otr.receiveMsg(query_msg); if (query_msg) {
} else { this.otr.receiveMsg(query_msg);
this.otr.sendQueryMsg(); } else {
} 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'))) {
this.otr.receiveMsg(text); if (text.match(/^\?OTRv23?/)) {
this.initiateOTR(text);
} else {
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 () {