Source: jsxc.lib.otr.js

  1. /**
  2. * @namespace jsxc.otr
  3. */
  4. jsxc.otr = {
  5. /** list of otr objects */
  6. objects: {},
  7. dsaFallback: null,
  8. /**
  9. * Handler for otr receive event
  10. *
  11. * @memberOf jsxc.otr
  12. * @param {Object} d
  13. * @param {string} d.bid
  14. * @param {string} d.msg received message
  15. * @param {boolean} d.encrypted True, if msg was encrypted.
  16. * @param {boolean} d.forwarded
  17. * @param {string} d.stamp timestamp
  18. */
  19. receiveMessage: function(d) {
  20. var bid = d.bid;
  21. if (jsxc.otr.objects[bid].msgstate !== OTR.CONST.MSGSTATE_PLAINTEXT) {
  22. jsxc.otr.backup(bid);
  23. }
  24. if (jsxc.otr.objects[bid].msgstate !== OTR.CONST.MSGSTATE_PLAINTEXT && !d.encrypted) {
  25. jsxc.gui.window.postMessage({
  26. bid: bid,
  27. direction: jsxc.Message.SYS,
  28. msg: $.t('Received_an_unencrypted_message') + '. [' + d.msg + ']',
  29. encrypted: d.encrypted,
  30. forwarded: d.forwarded,
  31. stamp: d.stamp
  32. });
  33. } else {
  34. jsxc.gui.window.postMessage({
  35. _uid: d._uid,
  36. bid: bid,
  37. direction: jsxc.Message.IN,
  38. msg: d.msg,
  39. encrypted: d.encrypted,
  40. forwarded: d.forwarded,
  41. stamp: d.stamp,
  42. attachment: d.attachment
  43. });
  44. }
  45. },
  46. /**
  47. * Handler for otr send event
  48. *
  49. * @param {string} jid
  50. * @param {string} msg message to be send
  51. */
  52. sendMessage: function(jid, msg, message) {
  53. if (jsxc.otr.objects[jsxc.jidToBid(jid)].msgstate !== 0) {
  54. jsxc.otr.backup(jsxc.jidToBid(jid));
  55. }
  56. jsxc.xmpp._sendMessage(jid, msg, message);
  57. },
  58. /**
  59. * Create new otr instance
  60. *
  61. * @param {type} bid
  62. * @returns {undefined}
  63. */
  64. create: function(bid) {
  65. if (jsxc.otr.objects.hasOwnProperty(bid)) {
  66. return;
  67. }
  68. if (!jsxc.options.otr.priv) {
  69. return;
  70. }
  71. // save list of otr objects
  72. var ol = jsxc.storage.getUserItem('otrlist') || [];
  73. if (ol.indexOf(bid) < 0) {
  74. ol.push(bid);
  75. jsxc.storage.setUserItem('otrlist', ol);
  76. }
  77. jsxc.otr.objects[bid] = new OTR(jsxc.options.otr);
  78. if (jsxc.options.otr.SEND_WHITESPACE_TAG) {
  79. jsxc.otr.objects[bid].SEND_WHITESPACE_TAG = true;
  80. }
  81. if (jsxc.options.otr.WHITESPACE_START_AKE) {
  82. jsxc.otr.objects[bid].WHITESPACE_START_AKE = true;
  83. }
  84. jsxc.otr.objects[bid].on('status', function(status) {
  85. var data = jsxc.storage.getUserItem('buddy', bid);
  86. if (data === null) {
  87. return;
  88. }
  89. switch (status) {
  90. case OTR.CONST.STATUS_SEND_QUERY:
  91. jsxc.gui.window.postMessage({
  92. bid: bid,
  93. direction: jsxc.Message.SYS,
  94. msg: $.t('trying_to_start_private_conversation')
  95. });
  96. break;
  97. case OTR.CONST.STATUS_AKE_SUCCESS:
  98. data.fingerprint = jsxc.otr.objects[bid].their_priv_pk.fingerprint();
  99. data.msgstate = OTR.CONST.MSGSTATE_ENCRYPTED;
  100. var msg_state = jsxc.otr.objects[bid].trust ? 'Verified' : 'Unverified';
  101. var msg = $.t(msg_state + '_private_conversation_started');
  102. jsxc.gui.window.postMessage({
  103. bid: bid,
  104. direction: 'sys',
  105. msg: msg
  106. });
  107. break;
  108. case OTR.CONST.STATUS_END_OTR:
  109. data.fingerprint = null;
  110. if (jsxc.otr.objects[bid].msgstate === OTR.CONST.MSGSTATE_PLAINTEXT) {
  111. // we abort the private conversation
  112. data.msgstate = OTR.CONST.MSGSTATE_PLAINTEXT;
  113. jsxc.gui.window.postMessage({
  114. bid: bid,
  115. direction: jsxc.Message.SYS,
  116. msg: $.t('private_conversation_aborted')
  117. });
  118. } else {
  119. // the buddy abort the private conversation
  120. data.msgstate = OTR.CONST.MSGSTATE_FINISHED;
  121. jsxc.gui.window.postMessage({
  122. bid: bid,
  123. direction: jsxc.Message.SYS,
  124. msg: $.t('your_buddy_closed_the_private_conversation_you_should_do_the_same')
  125. });
  126. }
  127. break;
  128. case OTR.CONST.STATUS_SMP_HANDLE:
  129. jsxc.keepBusyAlive();
  130. break;
  131. }
  132. jsxc.storage.setUserItem('buddy', bid, data);
  133. // for encryption and verification state
  134. jsxc.gui.update(bid);
  135. });
  136. jsxc.otr.objects[bid].on('smp', function(type, data) {
  137. switch (type) {
  138. case 'question': // verification request received
  139. jsxc.gui.window.postMessage({
  140. bid: bid,
  141. direction: jsxc.Message.SYS,
  142. msg: $.t('Authentication_request_received')
  143. });
  144. jsxc.gui.window.smpRequest(bid, data);
  145. jsxc.storage.setUserItem('smp', bid, {
  146. data: data || null
  147. });
  148. break;
  149. case 'trust': // verification completed
  150. jsxc.otr.objects[bid].trust = data;
  151. jsxc.storage.updateUserItem('buddy', bid, 'trust', data);
  152. jsxc.otr.backup(bid);
  153. jsxc.gui.update(bid);
  154. if (data) {
  155. jsxc.gui.window.postMessage({
  156. bid: bid,
  157. direction: jsxc.Message.SYS,
  158. msg: $.t('conversation_is_now_verified')
  159. });
  160. } else {
  161. jsxc.gui.window.postMessage({
  162. bid: bid,
  163. direction: jsxc.Message.SYS,
  164. msg: $.t('authentication_failed')
  165. });
  166. }
  167. jsxc.storage.removeUserItem('smp', bid);
  168. jsxc.gui.dialog.close('smp');
  169. break;
  170. case 'abort':
  171. jsxc.gui.window.hideOverlay(bid);
  172. jsxc.gui.window.postMessage({
  173. bid: bid,
  174. direction: jsxc.Message.SYS,
  175. msg: $.t('Authentication_aborted')
  176. });
  177. break;
  178. default:
  179. jsxc.debug('[OTR] sm callback: Unknown type: ' + type);
  180. }
  181. });
  182. // Receive message
  183. jsxc.otr.objects[bid].on('ui', function(msg, encrypted, meta) {
  184. jsxc.otr.receiveMessage({
  185. _uid: meta._uid,
  186. bid: bid,
  187. msg: msg,
  188. encrypted: encrypted === true,
  189. stamp: meta.stamp,
  190. forwarded: meta.forwarded,
  191. attachment: meta.attachment
  192. });
  193. });
  194. // Send message
  195. jsxc.otr.objects[bid].on('io', function(msg, message) {
  196. var jid = jsxc.gui.window.get(bid).data('jid') || jsxc.otr.objects[bid].jid;
  197. jsxc.otr.objects[bid].jid = jid;
  198. jsxc.otr.sendMessage(jid, msg, message);
  199. });
  200. jsxc.otr.objects[bid].on('error', function(err) {
  201. // Handle this case in jsxc.otr.receiveMessage
  202. if (err !== 'Received an unencrypted message.') {
  203. jsxc.gui.window.postMessage({
  204. bid: bid,
  205. direction: jsxc.Message.SYS,
  206. msg: '[OTR] ' + $.t(err)
  207. });
  208. }
  209. jsxc.error('[OTR] ' + err);
  210. });
  211. jsxc.otr.restore(bid);
  212. },
  213. /**
  214. * show verification dialog with related part (secret or question)
  215. *
  216. * @param {type} bid
  217. * @param {string} [data]
  218. * @returns {undefined}
  219. */
  220. onSmpQuestion: function(bid, data) {
  221. jsxc.gui.showVerification(bid);
  222. $('#jsxc_dialog select').prop('selectedIndex', (data ? 2 : 3)).change();
  223. $('#jsxc_dialog > div:eq(0)').hide();
  224. if (data) {
  225. $('#jsxc_dialog > div:eq(2)').find('#jsxc_quest').val(data).prop('disabled', true);
  226. $('#jsxc_dialog > div:eq(2)').find('.jsxc_submit').text($.t('Answer'));
  227. $('#jsxc_dialog > div:eq(2)').find('.jsxc_explanation').text($.t('onsmp_explanation_question'));
  228. $('#jsxc_dialog > div:eq(2)').show();
  229. } else {
  230. $('#jsxc_dialog > div:eq(3)').find('.jsxc_explanation').text($.t('onsmp_explanation_secret'));
  231. $('#jsxc_dialog > div:eq(3)').show();
  232. }
  233. $('#jsxc_dialog .jsxc_close').click(function() {
  234. jsxc.storage.removeUserItem('smp', bid);
  235. if (jsxc.master) {
  236. jsxc.otr.objects[bid].sm.abort();
  237. }
  238. });
  239. },
  240. /**
  241. * Send verification request to buddy
  242. *
  243. * @param {string} bid
  244. * @param {string} sec secret
  245. * @param {string} [quest] question
  246. * @returns {undefined}
  247. */
  248. sendSmpReq: function(bid, sec, quest) {
  249. jsxc.keepBusyAlive();
  250. jsxc.otr.objects[bid].smpSecret(sec, quest || '');
  251. },
  252. /**
  253. * Toggle encryption state
  254. *
  255. * @param {type} bid
  256. * @returns {undefined}
  257. */
  258. toggleTransfer: function(bid) {
  259. if (typeof OTR !== 'function') {
  260. return;
  261. }
  262. if (jsxc.storage.getUserItem('buddy', bid).msgstate === 0) {
  263. jsxc.otr.goEncrypt(bid);
  264. } else {
  265. jsxc.otr.goPlain(bid);
  266. }
  267. },
  268. /**
  269. * Send request to encrypt the session
  270. *
  271. * @param {type} bid
  272. * @returns {undefined}
  273. */
  274. goEncrypt: function(bid) {
  275. if (jsxc.master) {
  276. if (jsxc.otr.objects.hasOwnProperty(bid)) {
  277. jsxc.otr.objects[bid].sendQueryMsg();
  278. }
  279. } else {
  280. jsxc.storage.updateUserItem('buddy', bid, 'transferReq', 1);
  281. }
  282. },
  283. /**
  284. * Abort encryptet session
  285. *
  286. * @param {type} bid
  287. * @param cb callback
  288. * @returns {undefined}
  289. */
  290. goPlain: function(bid, cb) {
  291. if (jsxc.master) {
  292. if (jsxc.otr.objects.hasOwnProperty(bid)) {
  293. jsxc.otr.objects[bid].endOtr.call(jsxc.otr.objects[bid], cb);
  294. jsxc.otr.objects[bid].init.call(jsxc.otr.objects[bid]);
  295. jsxc.otr.backup(bid);
  296. }
  297. } else {
  298. jsxc.storage.updateUserItem('buddy', bid, 'transferReq', 0);
  299. }
  300. },
  301. /**
  302. * Backups otr session
  303. *
  304. * @param {string} bid
  305. */
  306. backup: function(bid) {
  307. var o = jsxc.otr.objects[bid]; // otr object
  308. var r = {}; // return value
  309. if (o === null) {
  310. return;
  311. }
  312. // all variables which should be saved
  313. 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'];
  314. var i;
  315. for (i = 0; i < savekey.length; i++) {
  316. r[savekey[i]] = JSON.stringify(o[savekey[i]]);
  317. }
  318. if (o.their_priv_pk !== null) {
  319. r.their_priv_pk = JSON.stringify(o.their_priv_pk.packPublic());
  320. }
  321. if (o.ake.otr_version && o.ake.otr_version !== '') {
  322. r.otr_version = JSON.stringify(o.ake.otr_version);
  323. }
  324. jsxc.storage.setUserItem('otr', bid, r);
  325. },
  326. /**
  327. * Restore old otr session
  328. *
  329. * @param {string} bid
  330. */
  331. restore: function(bid) {
  332. var o = jsxc.otr.objects[bid];
  333. var d = jsxc.storage.getUserItem('otr', bid);
  334. if (o !== null || d !== null) {
  335. var key;
  336. for (key in d) {
  337. if (d.hasOwnProperty(key)) {
  338. var val = JSON.parse(d[key]);
  339. if (key === 'their_priv_pk' && val !== null) {
  340. val = DSA.parsePublic(val);
  341. }
  342. if (key === 'otr_version' && val !== null) {
  343. o.ake.otr_version = val;
  344. } else {
  345. o[key] = val;
  346. }
  347. }
  348. }
  349. jsxc.otr.objects[bid] = o;
  350. if (o.msgstate === 1 && o.their_priv_pk !== null) {
  351. o._smInit.call(jsxc.otr.objects[bid]);
  352. }
  353. }
  354. jsxc.otr.enable(bid);
  355. },
  356. /**
  357. * Create or load DSA key
  358. *
  359. * @returns {unresolved}
  360. */
  361. createDSA: function() {
  362. if (jsxc.options.otr.priv) {
  363. return;
  364. }
  365. if (typeof OTR !== 'function') {
  366. jsxc.warn('OTR support disabled');
  367. OTR = {};
  368. OTR.CONST = {
  369. MSGSTATE_PLAINTEXT: 0,
  370. MSGSTATE_ENCRYPTED: 1,
  371. MSGSTATE_FINISHED: 2
  372. };
  373. return;
  374. }
  375. if (jsxc.storage.getUserItem('key') === null) {
  376. var msg = $.t('Creating_your_private_key_');
  377. var worker = null;
  378. if (Worker) {
  379. // try to create web-worker
  380. try {
  381. worker = new Worker(jsxc.options.root + '/lib/otr/lib/dsa-webworker.js');
  382. } catch (err) {
  383. jsxc.warn('Couldn\'t create web-worker.', err);
  384. }
  385. }
  386. jsxc.otr.dsaFallback = (worker === null);
  387. if (!jsxc.otr.dsaFallback) {
  388. // create DSA key in background
  389. worker.onmessage = function(e) {
  390. var type = e.data.type;
  391. var val = e.data.val;
  392. if (type === 'debug') {
  393. jsxc.debug(val);
  394. } else if (type === 'data') {
  395. jsxc.otr.DSAready(DSA.parsePrivate(val));
  396. }
  397. };
  398. jsxc.debug('DSA key creation started.');
  399. // start worker
  400. worker.postMessage({
  401. imports: [jsxc.options.root + '/lib/otr/vendor/salsa20.js', jsxc.options.root + '/lib/otr/vendor/bigint.js', jsxc.options.root + '/lib/otr/vendor/crypto.js', jsxc.options.root + '/lib/otr/vendor/eventemitter.js', jsxc.options.root + '/lib/otr/lib/const.js', jsxc.options.root + '/lib/otr/lib/helpers.js', jsxc.options.root + '/lib/otr/lib/dsa.js'],
  402. seed: BigInt.getSeed(),
  403. debug: true
  404. });
  405. } else {
  406. // fallback
  407. jsxc.xmpp.conn.pause();
  408. jsxc.gui.dialog.open(jsxc.gui.template.get('waitAlert', null, msg), {
  409. noClose: true
  410. });
  411. jsxc.debug('DSA key creation started in fallback mode.');
  412. // wait until the wait alert is opened
  413. setTimeout(function() {
  414. var dsa = new DSA();
  415. jsxc.otr.DSAready(dsa);
  416. }, 500);
  417. }
  418. } else {
  419. jsxc.debug('DSA key loaded');
  420. jsxc.options.otr.priv = DSA.parsePrivate(jsxc.storage.getUserItem('key'));
  421. jsxc.otr._createDSA();
  422. }
  423. },
  424. /**
  425. * Ending of createDSA().
  426. */
  427. _createDSA: function() {
  428. jsxc.storage.setUserItem('priv_fingerprint', jsxc.options.otr.priv.fingerprint());
  429. $.each(jsxc.storage.getUserItem('windowlist') || [], function(index, val) {
  430. jsxc.otr.create(val);
  431. });
  432. },
  433. /**
  434. * Ending of DSA key generation.
  435. *
  436. * @param {DSA} dsa DSA object
  437. */
  438. DSAready: function(dsa) {
  439. jsxc.storage.setUserItem('key', dsa.packPrivate());
  440. jsxc.options.otr.priv = dsa;
  441. // close wait alert
  442. if (jsxc.otr.dsaFallback) {
  443. jsxc.xmpp.conn.resume();
  444. jsxc.gui.dialog.close();
  445. }
  446. jsxc.otr._createDSA();
  447. },
  448. enable: function(bid) {
  449. jsxc.gui.window.get(bid).find('.jsxc_otr').removeClass('jsxc_disabled');
  450. }
  451. };