JSDoc: Source: jsxc.lib.otr.js

# Source: jsxc.lib.otr.js

    /**
     * @namespace jsxc.otr
     */
    jsxc.otr = {
       /** list of otr objects */
       objects: {},

       dsaFallback: null,
       /**
        * Handler for otr receive event
        *
        * @memberOf jsxc.otr
        * @param {Object} d
        * @param {string} d.bid
        * @param {string} d.msg received message
        * @param {boolean} d.encrypted True, if msg was encrypted.
        * @param {boolean} d.forwarded
        * @param {string} d.stamp timestamp
        */
       receiveMessage: function(d) {
          var bid = d.bid;

          if (jsxc.otr.objects[bid].msgstate !== OTR.CONST.MSGSTATE_PLAINTEXT) {
             jsxc.otr.backup(bid);
          }

          if (jsxc.otr.objects[bid].msgstate !== OTR.CONST.MSGSTATE_PLAINTEXT && !d.encrypted) {
             jsxc.gui.window.postMessage({
                bid: bid,
                direction: jsxc.Message.SYS,
                msg: $.t('Received_an_unencrypted_message') + '. [' + d.msg + ']',
                encrypted: d.encrypted,
                forwarded: d.forwarded,
                stamp: d.stamp
             });
          } else {
             jsxc.gui.window.postMessage({
                _uid: d._uid,
                bid: bid,
                direction: jsxc.Message.IN,
                msg: d.msg,
                encrypted: d.encrypted,
                forwarded: d.forwarded,
                stamp: d.stamp,
                attachment: d.attachment
             });
          }
       },

       /**
        * Handler for otr send event
        *
        * @param {string} jid
        * @param {string} msg message to be send
        */
       sendMessage: function(jid, msg, message) {
          if (jsxc.otr.objects[jsxc.jidToBid(jid)].msgstate !== 0) {
             jsxc.otr.backup(jsxc.jidToBid(jid));
          }

          jsxc.xmpp._sendMessage(jid, msg, message);
       },

       /**
        * Create new otr instance
        *
        * @param {type} bid
        * @returns {undefined}
        */
       create: function(bid) {

          if (jsxc.otr.objects.hasOwnProperty(bid)) {
             return;
          }

          if (!jsxc.options.otr.priv) {
             return;
          }

          // save list of otr objects
          var ol = jsxc.storage.getUserItem('otrlist') || [];
          if (ol.indexOf(bid) < 0) {
             ol.push(bid);
             jsxc.storage.setUserItem('otrlist', ol);
          }

          jsxc.otr.objects[bid] = new OTR(jsxc.options.otr);

          if (jsxc.options.otr.SEND_WHITESPACE_TAG) {
             jsxc.otr.objects[bid].SEND_WHITESPACE_TAG = true;
          }

          if (jsxc.options.otr.WHITESPACE_START_AKE) {
             jsxc.otr.objects[bid].WHITESPACE_START_AKE = true;
          }

          jsxc.otr.objects[bid].on('status', function(status) {
             var data = jsxc.storage.getUserItem('buddy', bid);

             if (data === null) {
                return;
             }

             switch (status) {
                case OTR.CONST.STATUS_SEND_QUERY:
                   jsxc.gui.window.postMessage({
                      bid: bid,
                      direction: jsxc.Message.SYS,
                      msg: $.t('trying_to_start_private_conversation')
                   });
                   break;
                case OTR.CONST.STATUS_AKE_SUCCESS:
                   data.fingerprint = jsxc.otr.objects[bid].their_priv_pk.fingerprint();
                   data.msgstate = OTR.CONST.MSGSTATE_ENCRYPTED;

                   var msg_state = jsxc.otr.objects[bid].trust ? 'Verified' : 'Unverified';
                   var msg = $.t(msg_state + '_private_conversation_started');

                   jsxc.gui.window.postMessage({
                      bid: bid,
                      direction: 'sys',
                      msg: msg
                   });
                   break;
                case OTR.CONST.STATUS_END_OTR:
                   data.fingerprint = null;

                   if (jsxc.otr.objects[bid].msgstate === OTR.CONST.MSGSTATE_PLAINTEXT) {
                      // we abort the private conversation

                      data.msgstate = OTR.CONST.MSGSTATE_PLAINTEXT;
                      jsxc.gui.window.postMessage({
                         bid: bid,
                         direction: jsxc.Message.SYS,
                         msg: $.t('private_conversation_aborted')
                      });

                   } else {
                      // the buddy abort the private conversation

                      data.msgstate = OTR.CONST.MSGSTATE_FINISHED;
                      jsxc.gui.window.postMessage({
                         bid: bid,
                         direction: jsxc.Message.SYS,
                         msg: $.t('your_buddy_closed_the_private_conversation_you_should_do_the_same')
                      });
                   }
                   break;
                case OTR.CONST.STATUS_SMP_HANDLE:
                   jsxc.keepBusyAlive();
                   break;
             }

             jsxc.storage.setUserItem('buddy', bid, data);

             // for encryption and verification state
             jsxc.gui.update(bid);
          });

          jsxc.otr.objects[bid].on('smp', function(type, data) {
             switch (type) {
                case 'question': // verification request received
                   jsxc.gui.window.postMessage({
                      bid: bid,
                      direction: jsxc.Message.SYS,
                      msg: $.t('Authentication_request_received')
                   });

                   jsxc.gui.window.smpRequest(bid, data);
                   jsxc.storage.setUserItem('smp', bid, {
                      data: data || null
                   });

                   break;
                case 'trust': // verification completed
                   jsxc.otr.objects[bid].trust = data;
                   jsxc.storage.updateUserItem('buddy', bid, 'trust', data);
                   jsxc.otr.backup(bid);
                   jsxc.gui.update(bid);

                   if (data) {
                      jsxc.gui.window.postMessage({
                         bid: bid,
                         direction: jsxc.Message.SYS,
                         msg: $.t('conversation_is_now_verified')
                      });
                   } else {
                      jsxc.gui.window.postMessage({
                         bid: bid,
                         direction: jsxc.Message.SYS,
                         msg: $.t('authentication_failed')
                      });
                   }
                   jsxc.storage.removeUserItem('smp', bid);
                   jsxc.gui.dialog.close('smp');
                   break;
                case 'abort':
                   jsxc.gui.window.hideOverlay(bid);
                   jsxc.gui.window.postMessage({
                      bid: bid,
                      direction: jsxc.Message.SYS,
                      msg: $.t('Authentication_aborted')
                   });
                   break;
                default:
                   jsxc.debug('[OTR] sm callback: Unknown type: ' + type);
             }
          });

          // Receive message
          jsxc.otr.objects[bid].on('ui', function(msg, encrypted, meta) {
             jsxc.otr.receiveMessage({
                _uid: meta._uid,
                bid: bid,
                msg: msg,
                encrypted: encrypted === true,
                stamp: meta.stamp,
                forwarded: meta.forwarded,
                attachment: meta.attachment
             });
          });

          // Send message
          jsxc.otr.objects[bid].on('io', function(msg, message) {
             var jid = jsxc.gui.window.get(bid).data('jid') || jsxc.otr.objects[bid].jid;

             jsxc.otr.objects[bid].jid = jid;

             jsxc.otr.sendMessage(jid, msg, message);
          });

          jsxc.otr.objects[bid].on('error', function(err) {
             // Handle this case in jsxc.otr.receiveMessage
             if (err !== 'Received an unencrypted message.') {
                jsxc.gui.window.postMessage({
                   bid: bid,
                   direction: jsxc.Message.SYS,
                   msg: '[OTR] ' + $.t(err)
                });
             }

             jsxc.error('[OTR] ' + err);
          });

          jsxc.otr.restore(bid);
       },

       /**
        * show verification dialog with related part (secret or question)
        *
        * @param {type} bid
        * @param {string} [data]
        * @returns {undefined}
        */
       onSmpQuestion: function(bid, data) {
          jsxc.gui.showVerification(bid);

          $('#jsxc_dialog select').prop('selectedIndex', (data ? 2 : 3)).change();
          $('#jsxc_dialog > div:eq(0)').hide();

          if (data) {
             $('#jsxc_dialog > div:eq(2)').find('#jsxc_quest').val(data).prop('disabled', true);
             $('#jsxc_dialog > div:eq(2)').find('.jsxc_submit').text($.t('Answer'));
             $('#jsxc_dialog > div:eq(2)').find('.jsxc_explanation').text($.t('onsmp_explanation_question'));
             $('#jsxc_dialog > div:eq(2)').show();
          } else {
             $('#jsxc_dialog > div:eq(3)').find('.jsxc_explanation').text($.t('onsmp_explanation_secret'));
             $('#jsxc_dialog > div:eq(3)').show();
          }

          $('#jsxc_dialog .jsxc_close').click(function() {
             jsxc.storage.removeUserItem('smp', bid);

             if (jsxc.master) {
                jsxc.otr.objects[bid].sm.abort();
             }
          });
       },

       /**
        * Send verification request to buddy
        *
        * @param {string} bid
        * @param {string} sec secret
        * @param {string} [quest] question
        * @returns {undefined}
        */
       sendSmpReq: function(bid, sec, quest) {
          jsxc.keepBusyAlive();

          jsxc.otr.objects[bid].smpSecret(sec, quest || '');
       },

       /**
        * Toggle encryption state
        *
        * @param {type} bid
        * @returns {undefined}
        */
       toggleTransfer: function(bid) {
          if (typeof OTR !== 'function') {
             return;
          }

          if (jsxc.storage.getUserItem('buddy', bid).msgstate === 0) {
             jsxc.otr.goEncrypt(bid);
          } else {
             jsxc.otr.goPlain(bid);
          }
       },

       /**
        * Send request to encrypt the session
        *
        * @param {type} bid
        * @returns {undefined}
        */
       goEncrypt: function(bid) {
          if (jsxc.master) {
             if (jsxc.otr.objects.hasOwnProperty(bid)) {
                jsxc.otr.objects[bid].sendQueryMsg();
             }
          } else {
             jsxc.storage.updateUserItem('buddy', bid, 'transferReq', 1);
          }
       },

       /**
        * Abort encryptet session
        *
        * @param {type} bid
        * @param cb callback
        * @returns {undefined}
        */
       goPlain: function(bid, cb) {
          if (jsxc.master) {
             if (jsxc.otr.objects.hasOwnProperty(bid)) {
                jsxc.otr.objects[bid].endOtr.call(jsxc.otr.objects[bid], cb);
                jsxc.otr.objects[bid].init.call(jsxc.otr.objects[bid]);

                jsxc.otr.backup(bid);
             }
          } else {
             jsxc.storage.updateUserItem('buddy', bid, 'transferReq', 0);
          }
       },

       /**
        * Backups otr session
        *
        * @param {string} bid
        */
       backup: function(bid) {
          var o = jsxc.otr.objects[bid]; // otr object
          var r = {}; // return value

          if (o === null) {
             return;
          }

          // all variables which should be saved
          var savekey = ['jid', 'our_instance_tag', 'msgstate', 'authstate', 'fragment', 'their_y', 'their_old_y', 'their_keyid', 'their_instance_tag', 'our_dh', 'our_old_dh', 'our_keyid', 'sessKeys', 'storedMgs', 'oldMacKeys', 'trust', 'transmittedRS', 'ssid', 'receivedPlaintext', 'authstate', 'send_interval'];

          var i;
          for (i = 0; i < savekey.length; i++) {
             r[savekey[i]] = JSON.stringify(o[savekey[i]]);
          }

          if (o.their_priv_pk !== null) {
             r.their_priv_pk = JSON.stringify(o.their_priv_pk.packPublic());
          }

          if (o.ake.otr_version && o.ake.otr_version !== '') {
             r.otr_version = JSON.stringify(o.ake.otr_version);
          }

          jsxc.storage.setUserItem('otr', bid, r);
       },

       /**
        * Restore old otr session
        *
        * @param {string} bid
        */
       restore: function(bid) {
          var o = jsxc.otr.objects[bid];
          var d = jsxc.storage.getUserItem('otr', bid);

          if (o !== null || d !== null) {
             var key;
             for (key in d) {
                if (d.hasOwnProperty(key)) {
                   var val = JSON.parse(d[key]);
                   if (key === 'their_priv_pk' && val !== null) {
                      val = DSA.parsePublic(val);
                   }
                   if (key === 'otr_version' && val !== null) {
                      o.ake.otr_version = val;
                   } else {
                      o[key] = val;
                   }
                }
             }

             jsxc.otr.objects[bid] = o;

             if (o.msgstate === 1 && o.their_priv_pk !== null) {
                o._smInit.call(jsxc.otr.objects[bid]);
             }
          }

          jsxc.otr.enable(bid);
       },

       /**
        * Create or load DSA key
        *
        * @returns {unresolved}
        */
       createDSA: function() {
          if (jsxc.options.otr.priv) {
             return;
          }

          if (typeof OTR !== 'function') {
             jsxc.warn('OTR support disabled');

             OTR = {};
             OTR.CONST = {
                MSGSTATE_PLAINTEXT: 0,
                MSGSTATE_ENCRYPTED: 1,
                MSGSTATE_FINISHED: 2
             };

             return;
          }

          if (jsxc.storage.getUserItem('key') === null) {
             var msg = $.t('Creating_your_private_key_');
             var worker = null;

             if (Worker) {
                // try to create web-worker

                var scriptPath = $('script').map(function() {
                   var el = $(this);
                   var src = el.attr('src') || '';
                   var matches = src.match(/(.+\/)jsxc\.(min\.)?js$/);

                   if (matches) {
                      return matches[1];
                   }
                });

                if (scriptPath.length === 0) {
                   jsxc.debug('Could not determine script path for web worker.');

                   scriptPath[0] = jsxc.options.root + '/';
                } else if (scriptPath.length !== 1) {
                   jsxc.debug('We found multiple (' + scriptPath.length + ') script paths.');
                }

                try {
                   worker = new Worker(scriptPath[0] + 'lib/jsxc.otr.webworker.js');
                } catch (err) {
                   jsxc.warn('Couldn\'t create web-worker.', err);
                }
             }

             jsxc.otr.dsaFallback = (worker === null);

             if (!jsxc.otr.dsaFallback) {
                // create DSA key in background

                worker.onmessage = function(e) {
                   var type = e.data.type;
                   var val = e.data.val;

                   if (type === 'debug') {
                      jsxc.debug(val);
                   } else if (type === 'data') {
                      jsxc.otr.DSAready(DSA.parsePrivate(val));
                   }
                };

                jsxc.debug('DSA key creation started.');

                // start worker
                worker.postMessage({
                   imports: [],
                   seed: BigInt.getSeed(),
                   debug: true
                });

             } else {
                // fallback
                jsxc.xmpp.conn.pause();

                jsxc.gui.dialog.open(jsxc.gui.template.get('waitAlert', null, msg), {
                   noClose: true
                });

                jsxc.debug('DSA key creation started in fallback mode.');

                // wait until the wait alert is opened
                setTimeout(function() {
                   var dsa = new DSA();
                   jsxc.otr.DSAready(dsa);
                }, 500);
             }
          } else {
             jsxc.debug('DSA key loaded');
             jsxc.options.otr.priv = DSA.parsePrivate(jsxc.storage.getUserItem('key'));

             jsxc.otr._createDSA();
          }
       },

       /**
        * Ending of createDSA().
        */
       _createDSA: function() {

          jsxc.storage.setUserItem('priv_fingerprint', jsxc.options.otr.priv.fingerprint());

          $.each(jsxc.storage.getUserItem('windowlist') || [], function(index, val) {
             jsxc.otr.create(val);
          });
       },

       /**
        * Ending of DSA key generation.
        *
        * @param {DSA} dsa DSA object
        */
       DSAready: function(dsa) {
          jsxc.storage.setUserItem('key', dsa.packPrivate());
          jsxc.options.otr.priv = dsa;

          // close wait alert
          if (jsxc.otr.dsaFallback) {
             jsxc.xmpp.conn.resume();
             jsxc.gui.dialog.close();
          }

          jsxc.otr._createDSA();
       },

       enable: function(bid) {
          jsxc.gui.window.get(bid).find('.jsxc_otr').removeClass('jsxc_disabled');
       }
    };