From 8232cdaff2d88963b88c694218fb31613bd4acf2 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Fri, 31 Jan 2014 05:50:38 +0200 Subject: [PATCH] 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). --- converse.js | 171 +++++++++++++++++++++++++++++----------------------- 1 file changed, 96 insertions(+), 75 deletions(-) diff --git a/converse.js b/converse.js index e1fce3174..1c63e1b8a 100644 --- a/converse.js +++ b/converse.js @@ -451,6 +451,24 @@ 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 () { if (this.debug) { this.connection.xmlInput = function (body) { console.log(body); }; @@ -465,21 +483,7 @@ this.initRoster(); this.chatboxes.onConnected(); this.connection.roster.get(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.registerGlobalEventHandlers(); this.giveFeedback(__('Online Contacts')); if (this.callback) { @@ -513,9 +517,6 @@ this.ChatBox = Backbone.Model.extend({ initialize: function () { if (this.get('box_id') !== 'controlbox') { - if (_.contains([UNVERIFIED, VERIFIED], this.get('otr_status'))) { - this.initiateOTR(); - } this.messages = new converse.Messages(); this.messages.localStorage = new Backbone.LocalStorage( hex_sha1('converse.messages'+this.get('jid')+converse.bare_jid)); @@ -527,43 +528,53 @@ } }, - getSession: function () { - // XXX: sessionStorage is not supported in IE < 8. Perhaps a + getSession: function (callback) { + // FIXME: sessionStorage is not supported in IE < 8. Perhaps a // user alert is required here... - var saved_key = window.sessionStorage[hex_sha1(this.id+'priv_key')]; - var instance_tag = window.sessionStorage[hex_sha1(this.id+'instance_tag')]; + var result, pass, instance_tag, saved_key; var cipher = CryptoJS.lib.PasswordBasedCipher; - var pass = converse.connection.pass; - var pass_check = this.get('pass_check'); - var result, key; - if (saved_key && instance_tag && typeof pass_check !== 'undefined') { - var decrypted = cipher.decrypt(CryptoJS.algo.AES, saved_key, pass); - key = DSA.parsePrivate(decrypted.toString(CryptoJS.enc.Latin1)); - if (cipher.decrypt(CryptoJS.algo.AES, pass_check, pass).toString(CryptoJS.enc.Latin1) === 'match') { - // Verified that the user's password is still the same - this.trigger('showHelpMessages', [__('Re-establishing encrypted session')]); - return { - 'key': key, - 'instance_tag': instance_tag - }; + 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'); + if (saved_key && instance_tag && typeof pass_check !== 'undefined') { + var decrypted = cipher.decrypt(CryptoJS.algo.AES, saved_key, pass); + var key = DSA.parsePrivate(decrypted.toString(CryptoJS.enc.Latin1)); + if (cipher.decrypt(CryptoJS.algo.AES, pass_check, pass).toString(CryptoJS.enc.Latin1) === 'match') { + // Verified that the user's password is still the same + this.trigger('showHelpMessages', [__('Re-establishing encrypted session')]); + 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.')]); - this.save({'pass_check': cipher.encrypt(CryptoJS.algo.AES, 'match', pass).toString()}); - return { - 'key': key, - 'instance_tag': instance_tag - }; + // We need to generate a new key and instance tag + instance_tag = OTR.makeInstanceTag(); + 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 + // 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) { @@ -616,32 +627,34 @@ // query message from our buddy. Otherwise, it is us who will // send the query message to them. this.save({'otr_status': UNENCRYPTED}); - var session = this.getSession(); - this.otr = new OTR({ - fragment_size: 140, - send_interval: 200, - priv: session.key, - instance_tag: session.instance_tag, - debug: this.debug - }); - this.otr.on('status', $.proxy(this.updateOTRStatus, this)); - this.otr.on('smp', $.proxy(this.onSMP, this)); + var session = this.getSession($.proxy(function (session) { + this.otr = new OTR({ + fragment_size: 140, + send_interval: 200, + priv: session.key, + instance_tag: session.instance_tag, + debug: this.debug + }); + this.otr.on('status', $.proxy(this.updateOTRStatus, this)); + this.otr.on('smp', $.proxy(this.onSMP, this)); - this.otr.on('ui', $.proxy(function (msg) { - this.trigger('showReceivedOTRMessage', msg); - }, this)); - this.otr.on('io', $.proxy(function (msg) { - this.trigger('sendMessageStanza', msg); - }, this)); - this.otr.on('error', $.proxy(function (msg) { - this.trigger('showOTRError', msg); - }, this)); + this.otr.on('ui', $.proxy(function (msg) { + this.trigger('showReceivedOTRMessage', msg); + }, this)); + this.otr.on('io', $.proxy(function (msg) { + this.trigger('sendMessageStanza', msg); + }, this)); + this.otr.on('error', $.proxy(function (msg) { + this.trigger('showOTRError', msg); + }, this)); - if (query_msg) { - this.otr.receiveMsg(query_msg); - } else { - this.otr.sendQueryMsg(); - } + this.trigger('showHelpMessages', [__('Exchanging private key with buddy.')]); + if (query_msg) { + this.otr.receiveMsg(query_msg); + } else { + this.otr.sendQueryMsg(); + } + }, this)); }, endOTR: function () { @@ -700,7 +713,11 @@ return this.createMessage(message); } 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 { if (text.match(/^\?OTR/)) { // They want to initiate OTR @@ -849,6 +866,10 @@ if (this.model.get('status')) { this.showStatusMessage(this.model.get('status')); } + + if (_.contains([UNVERIFIED, VERIFIED], this.model.get('otr_status'))) { + this.model.initiateOTR(); + } }, render: function () {