omemo.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. (function (root, factory) {
  2. define(["jasmine", "mock", "converse-core", "test-utils"], factory);
  3. } (this, function (jasmine, mock, converse, test_utils) {
  4. var Strophe = converse.env.Strophe;
  5. var b64_sha1 = converse.env.b64_sha1;
  6. var $iq = converse.env.$iq;
  7. var $msg = converse.env.$msg;
  8. var _ = converse.env._;
  9. var u = converse.env.utils;
  10. describe("The OMEMO module", function() {
  11. it("will add processing hints to sent out encrypted <message> stanzas",
  12. mock.initConverseWithPromises(
  13. null, ['rosterGroupsFetched'], {},
  14. function (done, _converse) {
  15. // TODO
  16. done();
  17. }));
  18. it("updates the user's device list based on PEP messages",
  19. mock.initConverseWithPromises(
  20. null, ['rosterGroupsFetched'], {},
  21. function (done, _converse) {
  22. let iq_stanza;
  23. test_utils.createContacts(_converse, 'current');
  24. const contact_jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@localhost';
  25. test_utils.waitUntil(function () {
  26. return _.filter(
  27. _converse.connection.IQ_stanzas,
  28. (iq) => {
  29. const node = iq.nodeTree.querySelector('iq[to="'+_converse.bare_jid+'"] query[node="eu.siacs.conversations.axolotl.devicelist"]');
  30. if (node) { iq_stanza = iq.nodeTree;}
  31. return node;
  32. }).length;
  33. }).then(function () {
  34. expect(iq_stanza.outerHTML).toBe(
  35. '<iq type="get" from="dummy@localhost" to="dummy@localhost" xmlns="jabber:client" id="'+iq_stanza.getAttribute("id")+'">'+
  36. '<query xmlns="http://jabber.org/protocol/disco#items" '+
  37. 'node="eu.siacs.conversations.axolotl.devicelist"/>'+
  38. '</iq>');
  39. const stanza = $iq({
  40. 'from': contact_jid,
  41. 'id': iq_stanza.getAttribute('id'),
  42. 'to': _converse.bare_jid,
  43. 'type': 'result',
  44. }).c('query', {
  45. 'xmlns': 'http://jabber.org/protocol/disco#items',
  46. 'node': 'eu.siacs.conversations.axolotl.devicelist'
  47. }).c('device', {'id': '555'}).up()
  48. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  49. expect(_converse.devicelists.length).toBe(1);
  50. const devicelist = _converse.devicelists.get(_converse.bare_jid);
  51. expect(devicelist.devices.length).toBe(1);
  52. expect(devicelist.devices.at(0).get('id')).toBe('555');
  53. return test_utils.waitUntil(() => _converse.devicelists);
  54. }).then(function () {
  55. // We simply emit, to avoid doing all the setup work
  56. _converse.emit('OMEMOInitialized');
  57. let stanza = $msg({
  58. 'from': contact_jid,
  59. 'to': _converse.bare_jid,
  60. 'type': 'headline',
  61. 'id': 'update_01',
  62. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  63. .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
  64. .c('item')
  65. .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
  66. .c('device', {'id': '1234'})
  67. .c('device', {'id': '4223'})
  68. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  69. expect(_converse.devicelists.length).toBe(2);
  70. let devices = _converse.devicelists.get(contact_jid).devices;
  71. expect(devices.length).toBe(2);
  72. expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('1234,4223');
  73. expect(devices.get('1234').get('active')).toBe(true);
  74. expect(devices.get('4223').get('active')).toBe(true);
  75. stanza = $msg({
  76. 'from': contact_jid,
  77. 'to': _converse.bare_jid,
  78. 'type': 'headline',
  79. 'id': 'update_02',
  80. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  81. .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
  82. .c('item')
  83. .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
  84. .c('device', {'id': '4223'})
  85. .c('device', {'id': '4224'})
  86. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  87. expect(_converse.devicelists.length).toBe(2);
  88. expect(devices.length).toBe(3);
  89. expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('1234,4223,4224');
  90. expect(devices.get('1234').get('active')).toBe(false);
  91. expect(devices.get('4223').get('active')).toBe(true);
  92. expect(devices.get('4224').get('active')).toBe(true);
  93. // Check that own devicelist gets updated
  94. stanza = $msg({
  95. 'from': _converse.bare_jid,
  96. 'to': _converse.bare_jid,
  97. 'type': 'headline',
  98. 'id': 'update_03',
  99. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  100. .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
  101. .c('item')
  102. .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
  103. .c('device', {'id': '555'})
  104. .c('device', {'id': '777'})
  105. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  106. expect(_converse.devicelists.length).toBe(2);
  107. devices = _converse.devicelists.get(_converse.bare_jid).devices;
  108. expect(devices.length).toBe(3);
  109. expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,555,777');
  110. expect(devices.get('123456789').get('active')).toBe(true);
  111. expect(devices.get('555').get('active')).toBe(true);
  112. expect(devices.get('777').get('active')).toBe(true);
  113. _converse.connection.IQ_stanzas = [];
  114. // Check that own device gets re-added
  115. stanza = $msg({
  116. 'from': _converse.bare_jid,
  117. 'to': _converse.bare_jid,
  118. 'type': 'headline',
  119. 'id': 'update_03',
  120. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  121. .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
  122. .c('item')
  123. .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
  124. .c('device', {'id': '444'})
  125. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  126. return test_utils.waitUntil(function () {
  127. return _.filter(
  128. _converse.connection.IQ_stanzas,
  129. (iq) => {
  130. const node = iq.nodeTree.querySelector('iq[from="'+_converse.bare_jid+'"] publish[node="eu.siacs.conversations.axolotl.devicelist"]');
  131. if (node) { iq_stanza = iq.nodeTree;}
  132. return node;
  133. }).length;
  134. });
  135. }).then(function () {
  136. // Check that our own device is added again, but that removed
  137. // devices are not added.
  138. expect(iq_stanza.outerHTML).toBe(
  139. '<iq from="dummy@localhost" type="set" xmlns="jabber:client" id="'+iq_stanza.getAttribute('id')+'">'+
  140. '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
  141. '<publish node="eu.siacs.conversations.axolotl.devicelist">'+
  142. '<item>'+
  143. '<list xmlns="eu.siacs.conversations.axolotl"/>'+
  144. '<device id="123456789"/>'+
  145. '<device id="444"/>'+
  146. '</item>'+
  147. '</publish>'+
  148. '</pubsub>'+
  149. '</iq>');
  150. expect(_converse.devicelists.length).toBe(2);
  151. const devices = _converse.devicelists.get(_converse.bare_jid).devices;
  152. // The device id for this device (123456789) was also generated and added to the list,
  153. // which is why we have 4 devices now.
  154. expect(devices.length).toBe(4);
  155. expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,444,555,777');
  156. expect(devices.get('123456789').get('active')).toBe(true);
  157. expect(devices.get('444').get('active')).toBe(true);
  158. expect(devices.get('555').get('active')).toBe(false);
  159. expect(devices.get('777').get('active')).toBe(false);
  160. done();
  161. });
  162. }));
  163. it("adds a toolbar button for starting an encrypted chat session",
  164. mock.initConverseWithPromises(
  165. null, ['rosterGroupsFetched'], {},
  166. function (done, _converse) {
  167. let iq_stanza;
  168. test_utils.createContacts(_converse, 'current');
  169. const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
  170. test_utils.waitUntil(function () {
  171. return _.filter(
  172. _converse.connection.IQ_stanzas,
  173. (iq) => {
  174. const node = iq.nodeTree.querySelector('iq[to="'+_converse.bare_jid+'"] query[node="eu.siacs.conversations.axolotl.devicelist"]');
  175. if (node) { iq_stanza = iq.nodeTree;}
  176. return node;
  177. }).length;
  178. }).then(function () {
  179. expect(iq_stanza.outerHTML).toBe(
  180. '<iq type="get" from="dummy@localhost" to="dummy@localhost" xmlns="jabber:client" id="'+iq_stanza.getAttribute("id")+'">'+
  181. '<query xmlns="http://jabber.org/protocol/disco#items" '+
  182. 'node="eu.siacs.conversations.axolotl.devicelist"/>'+
  183. '</iq>');
  184. const stanza = $iq({
  185. 'from': contact_jid,
  186. 'id': iq_stanza.getAttribute('id'),
  187. 'to': _converse.bare_jid,
  188. 'type': 'result',
  189. }).c('query', {
  190. 'xmlns': 'http://jabber.org/protocol/disco#items',
  191. 'node': 'eu.siacs.conversations.axolotl.devicelist'
  192. }).c('device', {'id': '482886413b977930064a5888b92134fe'}).up()
  193. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  194. expect(_converse.devicelists.length).toBe(1);
  195. const devicelist = _converse.devicelists.get(_converse.bare_jid);
  196. expect(devicelist.devices.length).toBe(1);
  197. expect(devicelist.devices.at(0).get('id')).toBe('482886413b977930064a5888b92134fe');
  198. test_utils.openChatBoxFor(_converse, contact_jid);
  199. return test_utils.waitUntil(() => {
  200. return _.filter(_converse.connection.IQ_stanzas, function (iq) {
  201. const node = iq.nodeTree.querySelector('publish[node="eu.siacs.conversations.axolotl.devicelist"]');
  202. if (node) { iq_stanza = iq.nodeTree; }
  203. return node;
  204. }).length;
  205. });
  206. }).then(function () {
  207. expect(iq_stanza.outerHTML).toBe(
  208. '<iq from="dummy@localhost" type="set" xmlns="jabber:client" id="'+iq_stanza.getAttribute('id')+'">'+
  209. '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
  210. '<publish node="eu.siacs.conversations.axolotl.devicelist">'+
  211. '<item>'+
  212. '<list xmlns="eu.siacs.conversations.axolotl"/>'+
  213. '<device id="482886413b977930064a5888b92134fe"/>'+
  214. '<device id="123456789"/>'+
  215. '</item>'+
  216. '</publish>'+
  217. '</pubsub>'+
  218. '</iq>');
  219. const stanza = $iq({
  220. 'from': _converse.bare_jid,
  221. 'id': iq_stanza.getAttribute('id'),
  222. 'to': _converse.bare_jid,
  223. 'type': 'result'});
  224. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  225. return test_utils.waitUntil(() => {
  226. return _.filter(_converse.connection.IQ_stanzas, function (iq) {
  227. const node = iq.nodeTree.querySelector('publish[node="eu.siacs.conversations.axolotl.bundles:123456789"]');
  228. if (node) { iq_stanza = iq.nodeTree; }
  229. return node;
  230. }).length;
  231. });
  232. }).then(function () {
  233. expect(iq_stanza.getAttributeNames().sort().join()).toBe(["from", "type", "xmlns", "id"].sort().join());
  234. expect(iq_stanza.querySelector('prekeys').childNodes.length).toBe(100);
  235. const signed_prekeys = iq_stanza.querySelectorAll('signedPreKeyPublic');
  236. expect(signed_prekeys.length).toBe(1);
  237. const signed_prekey = signed_prekeys[0];
  238. expect(signed_prekey.getAttribute('signedPreKeyId')).toBe('0')
  239. expect(iq_stanza.querySelectorAll('signedPreKeySignature').length).toBe(1);
  240. expect(iq_stanza.querySelectorAll('identityKey').length).toBe(1);
  241. const stanza = $iq({
  242. 'from': _converse.bare_jid,
  243. 'id': iq_stanza.getAttribute('id'),
  244. 'to': _converse.bare_jid,
  245. 'type': 'result'});
  246. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  247. return test_utils.waitUntil(() => {
  248. return _.filter(
  249. _converse.connection.IQ_stanzas,
  250. (iq) => {
  251. const node = iq.nodeTree.querySelector('iq[to="'+contact_jid+'"] query[node="eu.siacs.conversations.axolotl.devicelist"]');
  252. if (node) { iq_stanza = iq.nodeTree; }
  253. return node;
  254. }).length;});
  255. }).then(function () {
  256. expect(iq_stanza.outerHTML).toBe(
  257. '<iq type="get" from="dummy@localhost" to="'+contact_jid+'" xmlns="jabber:client" id="'+iq_stanza.getAttribute("id")+'">'+
  258. '<query xmlns="http://jabber.org/protocol/disco#items" '+
  259. 'node="eu.siacs.conversations.axolotl.devicelist"/>'+
  260. '</iq>');
  261. const stanza = $iq({
  262. 'from': contact_jid,
  263. 'id': iq_stanza.getAttribute('id'),
  264. 'to': _converse.bare_jid,
  265. 'type': 'result',
  266. }).c('query', {
  267. 'xmlns': 'http://jabber.org/protocol/disco#items',
  268. 'node': 'eu.siacs.conversations.axolotl.devicelist'
  269. }).c('device', {'id': '368866411b877c30064a5f62b917cffe'}).up()
  270. .c('device', {'id': '3300659945416e274474e469a1f0154c'}).up()
  271. .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
  272. .c('device', {'id': 'ae890ac52d0df67ed7cfdf51b644e901'});
  273. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  274. expect(_converse.devicelists.length).toBe(2);
  275. const devicelist = _converse.devicelists.get(contact_jid);
  276. expect(devicelist.devices.length).toBe(4);
  277. expect(devicelist.devices.at(0).get('id')).toBe('368866411b877c30064a5f62b917cffe');
  278. expect(devicelist.devices.at(1).get('id')).toBe('3300659945416e274474e469a1f0154c');
  279. expect(devicelist.devices.at(2).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
  280. expect(devicelist.devices.at(3).get('id')).toBe('ae890ac52d0df67ed7cfdf51b644e901');
  281. return test_utils.waitUntil(() => _converse.chatboxviews.get(contact_jid).el.querySelector('.chat-toolbar'));
  282. }).then(function () {
  283. const view = _converse.chatboxviews.get(contact_jid);
  284. const toolbar = view.el.querySelector('.chat-toolbar');
  285. expect(view.model.get('omemo_active')).toBe(undefined);
  286. const toggle = toolbar.querySelector('.toggle-omemo');
  287. expect(_.isNull(toggle)).toBe(false);
  288. expect(u.hasClass('fa-unlock', toggle)).toBe(true);
  289. expect(u.hasClass('fa-lock', toggle)).toBe(false);
  290. spyOn(view, 'toggleOMEMO').and.callThrough();
  291. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  292. toolbar.querySelector('.toggle-omemo').click();
  293. expect(view.toggleOMEMO).toHaveBeenCalled();
  294. expect(view.model.get('omemo_active')).toBe(true);
  295. return test_utils.waitUntil(() => u.hasClass('fa-lock', toolbar.querySelector('.toggle-omemo')));
  296. }).then(function () {
  297. const view = _converse.chatboxviews.get(contact_jid);
  298. const toolbar = view.el.querySelector('.chat-toolbar');
  299. const toggle = toolbar.querySelector('.toggle-omemo');
  300. expect(u.hasClass('fa-unlock', toggle)).toBe(false);
  301. expect(u.hasClass('fa-lock', toggle)).toBe(true);
  302. const textarea = view.el.querySelector('.chat-textarea');
  303. textarea.value = 'This message will be sent encrypted';
  304. view.keyPressed({
  305. target: textarea,
  306. preventDefault: _.noop,
  307. keyCode: 13
  308. });
  309. done();
  310. }).catch(_.partial(console.error, _));
  311. }));
  312. });
  313. describe("A chatbox with an active OMEMO session", function() {
  314. it("will not show the spoiler toolbar button",
  315. mock.initConverseWithPromises(
  316. null, ['rosterGroupsFetched'], {},
  317. function (done, _converse) {
  318. // TODO
  319. done()
  320. }));
  321. });
  322. }));