omemo.js 79 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452
  1. (function (root, factory) {
  2. define(["jasmine", "mock", "test-utils"], factory);
  3. } (this, function (jasmine, mock, test_utils) {
  4. const { $iq, $pres, $msg, _, Strophe } = converse.env;
  5. const u = converse.env.utils;
  6. function deviceListFetched (_converse, jid) {
  7. return _.filter(
  8. _converse.connection.IQ_stanzas,
  9. iq => iq.nodeTree.querySelector(`iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.devicelist"]`)
  10. ).pop();
  11. }
  12. function ownDeviceHasBeenPublished (_converse) {
  13. return _.filter(
  14. _converse.connection.IQ_stanzas,
  15. iq => iq.nodeTree.querySelector('iq[from="'+_converse.bare_jid+'"] publish[node="eu.siacs.conversations.axolotl.devicelist"]')
  16. ).pop();
  17. }
  18. function bundleHasBeenPublished (_converse) {
  19. return _.filter(
  20. _converse.connection.IQ_stanzas,
  21. iq => iq.nodeTree.querySelector('publish[node="eu.siacs.conversations.axolotl.bundles:123456789"]')
  22. ).pop();
  23. }
  24. function bundleFetched (_converse, jid, device_id) {
  25. return _.filter(
  26. _converse.connection.IQ_stanzas,
  27. (iq) => iq.nodeTree.querySelector(`iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.bundles:${device_id}"]`)
  28. ).pop();
  29. }
  30. async function initializedOMEMO (_converse) {
  31. await test_utils.waitUntilDiscoConfirmed(
  32. _converse, _converse.bare_jid,
  33. [{'category': 'pubsub', 'type': 'pep'}],
  34. ['http://jabber.org/protocol/pubsub#publish-options']
  35. );
  36. let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
  37. let stanza = $iq({
  38. 'from': _converse.bare_jid,
  39. 'id': iq_stanza.nodeTree.getAttribute('id'),
  40. 'to': _converse.bare_jid,
  41. 'type': 'result',
  42. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  43. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  44. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  45. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  46. .c('device', {'id': '482886413b977930064a5888b92134fe'});
  47. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  48. iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse))
  49. stanza = $iq({
  50. 'from': _converse.bare_jid,
  51. 'id': iq_stanza.nodeTree.getAttribute('id'),
  52. 'to': _converse.bare_jid,
  53. 'type': 'result'});
  54. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  55. iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse))
  56. stanza = $iq({
  57. 'from': _converse.bare_jid,
  58. 'id': iq_stanza.nodeTree.getAttribute('id'),
  59. 'to': _converse.bare_jid,
  60. 'type': 'result'});
  61. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  62. await _converse.api.waitUntil('OMEMOInitialized');
  63. }
  64. describe("The OMEMO module", function() {
  65. it("adds methods for encrypting and decrypting messages via AES GCM",
  66. mock.initConverseWithPromises(
  67. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  68. async function (done, _converse) {
  69. const message = 'This message will be encrypted'
  70. test_utils.createContacts(_converse, 'current', 1);
  71. _converse.emit('rosterContactsFetched');
  72. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  73. const view = await test_utils.openChatBoxFor(_converse, contact_jid);
  74. const payload = await view.model.encryptMessage(message);
  75. const result = await view.model.decryptMessage(payload);
  76. expect(result).toBe(message);
  77. done();
  78. }));
  79. it("enables encrypted messages to be sent and received",
  80. mock.initConverseWithPromises(
  81. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  82. async function (done, _converse) {
  83. let sent_stanza;
  84. test_utils.createContacts(_converse, 'current', 1);
  85. _converse.emit('rosterContactsFetched');
  86. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  87. await test_utils.waitUntil(() => initializedOMEMO(_converse));
  88. await test_utils.openChatBoxFor(_converse, contact_jid);
  89. let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
  90. let stanza = $iq({
  91. 'from': contact_jid,
  92. 'id': iq_stanza.nodeTree.getAttribute('id'),
  93. 'to': _converse.connection.jid,
  94. 'type': 'result',
  95. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  96. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  97. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  98. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  99. .c('device', {'id': '555'});
  100. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  101. await test_utils.waitUntil(() => _converse.omemo_store);
  102. const devicelist = _converse.devicelists.get({'jid': contact_jid});
  103. expect(devicelist.devices.length).toBe(1);
  104. const view = _converse.chatboxviews.get(contact_jid);
  105. view.model.set('omemo_active', true);
  106. const textarea = view.el.querySelector('.chat-textarea');
  107. textarea.value = 'This message will be encrypted';
  108. view.keyPressed({
  109. target: textarea,
  110. preventDefault: _.noop,
  111. keyCode: 13 // Enter
  112. });
  113. iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '555'));
  114. stanza = $iq({
  115. 'from': contact_jid,
  116. 'id': iq_stanza.nodeTree.getAttribute('id'),
  117. 'to': _converse.bare_jid,
  118. 'type': 'result',
  119. }).c('pubsub', {
  120. 'xmlns': 'http://jabber.org/protocol/pubsub'
  121. }).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:555"})
  122. .c('item')
  123. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  124. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('1111')).up()
  125. .c('signedPreKeySignature').t(btoa('2222')).up()
  126. .c('identityKey').t(btoa('3333')).up()
  127. .c('prekeys')
  128. .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1001')).up()
  129. .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up()
  130. .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
  131. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  132. iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'));
  133. stanza = $iq({
  134. 'from': _converse.bare_jid,
  135. 'id': iq_stanza.nodeTree.getAttribute('id'),
  136. 'to': _converse.bare_jid,
  137. 'type': 'result',
  138. }).c('pubsub', {
  139. 'xmlns': 'http://jabber.org/protocol/pubsub'
  140. }).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:482886413b977930064a5888b92134fe"})
  141. .c('item')
  142. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  143. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('100000')).up()
  144. .c('signedPreKeySignature').t(btoa('200000')).up()
  145. .c('identityKey').t(btoa('300000')).up()
  146. .c('prekeys')
  147. .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1991')).up()
  148. .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1992')).up()
  149. .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1993'));
  150. spyOn(_converse.connection, 'send').and.callFake(stanza => { sent_stanza = stanza });
  151. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  152. await test_utils.waitUntil(() => sent_stanza);
  153. expect(sent_stanza.toLocaleString()).toBe(
  154. `<message from="dummy@localhost/resource" id="${sent_stanza.nodeTree.getAttribute("id")}" `+
  155. `to="max.frankfurter@localhost" `+
  156. `type="chat" xmlns="jabber:client">`+
  157. `<body>This is an OMEMO encrypted message which your client doesn’t seem to support. Find more information on https://conversations.im/omemo</body>`+
  158. `<request xmlns="urn:xmpp:receipts"/>`+
  159. `<encrypted xmlns="eu.siacs.conversations.axolotl">`+
  160. `<header sid="123456789">`+
  161. `<key rid="482886413b977930064a5888b92134fe">YzFwaDNSNzNYNw==</key>`+
  162. `<key rid="555">YzFwaDNSNzNYNw==</key>`+
  163. `<iv>${sent_stanza.nodeTree.querySelector("iv").textContent}</iv>`+
  164. `</header>`+
  165. `<payload>${sent_stanza.nodeTree.querySelector("payload").textContent}</payload>`+
  166. `</encrypted>`+
  167. `<store xmlns="urn:xmpp:hints"/>`+
  168. `</message>`);
  169. // Test reception of an encrypted message
  170. let obj = await view.model.encryptMessage('This is an encrypted message from the contact')
  171. // XXX: Normally the key will be encrypted via libsignal.
  172. // However, we're mocking libsignal in the tests, so we include
  173. // it as plaintext in the message.
  174. stanza = $msg({
  175. 'from': contact_jid,
  176. 'to': _converse.connection.jid,
  177. 'type': 'chat',
  178. 'id': _converse.connection.getUniqueId()
  179. }).c('body').t('This is a fallback message').up()
  180. .c('encrypted', {'xmlns': Strophe.NS.OMEMO})
  181. .c('header', {'sid': '555'})
  182. .c('key', {'rid': _converse.omemo_store.get('device_id')}).t(u.arrayBufferToBase64(obj.key_and_tag)).up()
  183. .c('iv').t(obj.iv)
  184. .up().up()
  185. .c('payload').t(obj.payload);
  186. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  187. await new Promise((resolve, reject) => view.once('messageInserted', resolve));
  188. expect(view.model.messages.length).toBe(2);
  189. expect(view.el.querySelectorAll('.chat-msg__body')[1].textContent.trim())
  190. .toBe('This is an encrypted message from the contact');
  191. // #1193 Check for a received message without <body> tag
  192. obj = await view.model.encryptMessage('Another received encrypted message without fallback')
  193. stanza = $msg({
  194. 'from': contact_jid,
  195. 'to': _converse.connection.jid,
  196. 'type': 'chat',
  197. 'id': _converse.connection.getUniqueId()
  198. }).c('encrypted', {'xmlns': Strophe.NS.OMEMO})
  199. .c('header', {'sid': '555'})
  200. .c('key', {'rid': _converse.omemo_store.get('device_id')}).t(u.arrayBufferToBase64(obj.key_and_tag)).up()
  201. .c('iv').t(obj.iv)
  202. .up().up()
  203. .c('payload').t(obj.payload);
  204. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  205. await new Promise((resolve, reject) => view.once('messageInserted', resolve));
  206. await test_utils.waitUntil(() => view.model.messages.length > 1);
  207. expect(view.model.messages.length).toBe(3);
  208. expect(view.el.querySelectorAll('.chat-msg__body')[2].textContent.trim())
  209. .toBe('Another received encrypted message without fallback');
  210. done();
  211. }));
  212. it("enables encrypted groupchat messages to be sent and received",
  213. mock.initConverseWithPromises(
  214. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  215. async function (done, _converse) {
  216. // MEMO encryption works only in members only conferences
  217. // that are non-anonymous.
  218. const features = [
  219. 'http://jabber.org/protocol/muc',
  220. 'jabber:iq:register',
  221. 'muc_passwordprotected',
  222. 'muc_hidden',
  223. 'muc_temporary',
  224. 'muc_membersonly',
  225. 'muc_unmoderated',
  226. 'muc_nonanonymous'
  227. ];
  228. await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy', features);
  229. const view = _converse.chatboxviews.get('lounge@localhost');
  230. await test_utils.waitUntil(() => initializedOMEMO(_converse));
  231. const toolbar = view.el.querySelector('.chat-toolbar');
  232. let toggle = toolbar.querySelector('.toggle-omemo');
  233. toggle.click();
  234. expect(view.model.get('omemo_active')).toBe(true);
  235. const contact_jid = 'newguy@localhost';
  236. let stanza = $pres({
  237. 'to': 'dummy@localhost/resource',
  238. 'from': 'lounge@localhost/newguy'
  239. })
  240. .c('x', {xmlns: Strophe.NS.MUC_USER})
  241. .c('item', {
  242. 'affiliation': 'none',
  243. 'jid': 'newguy@localhost/_converse.js-290929789',
  244. 'role': 'participant'
  245. }).tree();
  246. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  247. let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
  248. expect(iq_stanza.toLocaleString()).toBe(
  249. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
  250. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  251. `<items node="eu.siacs.conversations.axolotl.devicelist"/>`+
  252. `</pubsub>`+
  253. `</iq>`);
  254. stanza = $iq({
  255. 'from': contact_jid,
  256. 'id': iq_stanza.nodeTree.getAttribute('id'),
  257. 'to': _converse.bare_jid,
  258. 'type': 'result',
  259. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  260. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  261. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  262. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  263. .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
  264. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  265. await test_utils.waitUntil(() => _converse.omemo_store);
  266. expect(_converse.devicelists.length).toBe(2);
  267. const devicelist = _converse.devicelists.get(contact_jid);
  268. expect(devicelist.devices.length).toBe(1);
  269. expect(devicelist.devices.at(0).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
  270. toggle = toolbar.querySelector('.toggle-omemo');
  271. expect(view.model.get('omemo_active')).toBe(true);
  272. expect(u.hasClass('fa-unlock', toggle)).toBe(false);
  273. expect(u.hasClass('fa-lock', toggle)).toBe(true);
  274. const textarea = view.el.querySelector('.chat-textarea');
  275. textarea.value = 'This message will be encrypted';
  276. view.keyPressed({
  277. target: textarea,
  278. preventDefault: _.noop,
  279. keyCode: 13 // Enter
  280. });
  281. iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '4e30f35051b7b8b42abe083742187228'));
  282. console.log("Bundle fetched 4e30f35051b7b8b42abe083742187228");
  283. stanza = $iq({
  284. 'from': contact_jid,
  285. 'id': iq_stanza.nodeTree.getAttribute('id'),
  286. 'to': _converse.bare_jid,
  287. 'type': 'result',
  288. }).c('pubsub', {
  289. 'xmlns': 'http://jabber.org/protocol/pubsub'
  290. }).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:4e30f35051b7b8b42abe083742187228"})
  291. .c('item')
  292. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  293. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('1111')).up()
  294. .c('signedPreKeySignature').t(btoa('2222')).up()
  295. .c('identityKey').t(btoa('3333')).up()
  296. .c('prekeys')
  297. .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1001')).up()
  298. .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up()
  299. .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
  300. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  301. iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'));
  302. console.log("Bundle fetched 482886413b977930064a5888b92134fe");
  303. stanza = $iq({
  304. 'from': _converse.bare_jid,
  305. 'id': iq_stanza.nodeTree.getAttribute('id'),
  306. 'to': _converse.bare_jid,
  307. 'type': 'result',
  308. }).c('pubsub', {
  309. 'xmlns': 'http://jabber.org/protocol/pubsub'
  310. }).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:482886413b977930064a5888b92134fe"})
  311. .c('item')
  312. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  313. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('100000')).up()
  314. .c('signedPreKeySignature').t(btoa('200000')).up()
  315. .c('identityKey').t(btoa('300000')).up()
  316. .c('prekeys')
  317. .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1991')).up()
  318. .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1992')).up()
  319. .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1993'));
  320. spyOn(_converse.connection, 'send');
  321. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  322. await test_utils.waitUntil(() => _converse.connection.send.calls.count());
  323. const sent_stanza = _converse.connection.send.calls.all()[0].args[0];
  324. expect(Strophe.serialize(sent_stanza)).toBe(
  325. `<message from="dummy@localhost/resource" `+
  326. `id="${sent_stanza.nodeTree.getAttribute("id")}" `+
  327. `to="lounge@localhost" `+
  328. `type="groupchat" `+
  329. `xmlns="jabber:client">`+
  330. `<body>This is an OMEMO encrypted message which your client doesn’t seem to support. Find more information on https://conversations.im/omemo</body>`+
  331. `<encrypted xmlns="eu.siacs.conversations.axolotl">`+
  332. `<header sid="123456789">`+
  333. `<key rid="482886413b977930064a5888b92134fe">YzFwaDNSNzNYNw==</key>`+
  334. `<key rid="4e30f35051b7b8b42abe083742187228">YzFwaDNSNzNYNw==</key>`+
  335. `<iv>${sent_stanza.nodeTree.querySelector("iv").textContent}</iv>`+
  336. `</header>`+
  337. `<payload>${sent_stanza.nodeTree.querySelector("payload").textContent}</payload>`+
  338. `</encrypted>`+
  339. `<store xmlns="urn:xmpp:hints"/>`+
  340. `</message>`);
  341. done();
  342. }));
  343. it("gracefully handles auth errors when trying to send encrypted groupchat messages",
  344. mock.initConverseWithPromises(
  345. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  346. async function (done, _converse) {
  347. // MEMO encryption works only in members only conferences
  348. // that are non-anonymous.
  349. const features = [
  350. 'http://jabber.org/protocol/muc',
  351. 'jabber:iq:register',
  352. 'muc_passwordprotected',
  353. 'muc_hidden',
  354. 'muc_temporary',
  355. 'muc_membersonly',
  356. 'muc_unmoderated',
  357. 'muc_nonanonymous'
  358. ];
  359. await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy', features);
  360. const view = _converse.chatboxviews.get('lounge@localhost');
  361. await test_utils.waitUntil(() => initializedOMEMO(_converse));
  362. const contact_jid = 'newguy@localhost';
  363. let stanza = $pres({
  364. 'to': 'dummy@localhost/resource',
  365. 'from': 'lounge@localhost/newguy'
  366. })
  367. .c('x', {xmlns: Strophe.NS.MUC_USER})
  368. .c('item', {
  369. 'affiliation': 'none',
  370. 'jid': 'newguy@localhost/_converse.js-290929789',
  371. 'role': 'participant'
  372. }).tree();
  373. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  374. const toolbar = view.el.querySelector('.chat-toolbar');
  375. const toggle = toolbar.querySelector('.toggle-omemo');
  376. toggle.click();
  377. expect(view.model.get('omemo_active')).toBe(true);
  378. expect(view.model.get('omemo_supported')).toBe(true);
  379. const textarea = view.el.querySelector('.chat-textarea');
  380. textarea.value = 'This message will be encrypted';
  381. view.keyPressed({
  382. target: textarea,
  383. preventDefault: _.noop,
  384. keyCode: 13 // Enter
  385. });
  386. let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
  387. expect(iq_stanza.toLocaleString()).toBe(
  388. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
  389. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  390. `<items node="eu.siacs.conversations.axolotl.devicelist"/>`+
  391. `</pubsub>`+
  392. `</iq>`);
  393. stanza = $iq({
  394. 'from': contact_jid,
  395. 'id': iq_stanza.nodeTree.getAttribute('id'),
  396. 'to': _converse.bare_jid,
  397. 'type': 'result',
  398. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  399. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  400. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  401. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  402. .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
  403. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  404. await test_utils.waitUntil(() => _converse.omemo_store);
  405. expect(_converse.devicelists.length).toBe(2);
  406. const devicelist = _converse.devicelists.get(contact_jid);
  407. expect(devicelist.devices.length).toBe(1);
  408. expect(devicelist.devices.at(0).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
  409. iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'));
  410. stanza = $iq({
  411. 'from': _converse.bare_jid,
  412. 'id': iq_stanza.nodeTree.getAttribute('id'),
  413. 'to': _converse.bare_jid,
  414. 'type': 'result',
  415. }).c('pubsub', {
  416. 'xmlns': 'http://jabber.org/protocol/pubsub'
  417. }).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:482886413b977930064a5888b92134fe"})
  418. .c('item')
  419. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  420. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('100000')).up()
  421. .c('signedPreKeySignature').t(btoa('200000')).up()
  422. .c('identityKey').t(btoa('300000')).up()
  423. .c('prekeys')
  424. .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1991')).up()
  425. .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1992')).up()
  426. .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1993'));
  427. iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '4e30f35051b7b8b42abe083742187228'));
  428. /* <iq xmlns="jabber:client" to="jc@opkode.com/converse.js-34183907" type="error" id="945c8ab3-b561-4d8a-92da-77c226bb1689:sendIQ" from="joris@konuro.net">
  429. * <pubsub xmlns="http://jabber.org/protocol/pubsub">
  430. * <items node="eu.siacs.conversations.axolotl.bundles:7580"/>
  431. * </pubsub>
  432. * <error code="401" type="auth">
  433. * <presence-subscription-required xmlns="http://jabber.org/protocol/pubsub#errors"/>
  434. * <not-authorized xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
  435. * </error>
  436. * </iq>
  437. */
  438. stanza = $iq({
  439. 'from': contact_jid,
  440. 'id': iq_stanza.nodeTree.getAttribute('id'),
  441. 'to': _converse.bare_jid,
  442. 'type': 'result',
  443. }).c('pubsub', {'xmlns': 'http://jabber.org/protocol/pubsub'})
  444. .c('items', {'node': "eu.siacs.conversations.axolotl.bundles:4e30f35051b7b8b42abe083742187228"}).up().up()
  445. .c('error', {'code': '401', 'type': 'auth'})
  446. .c('presence-subscription-required', {'xmlns':"http://jabber.org/protocol/pubsub#errors" }).up()
  447. .c('not-authorized', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
  448. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  449. await test_utils.waitUntil(() => document.querySelectorAll('.alert-danger').length, 2000);
  450. const header = document.querySelector('.alert-danger .modal-title');
  451. expect(header.textContent).toBe("Error");
  452. expect(u.ancestor(header, '.modal-content').querySelector('.modal-body p').textContent.trim())
  453. .toBe("Sorry, we're unable to send an encrypted message because newguy@localhost requires you "+
  454. "to be subscribed to their presence in order to see their OMEMO information");
  455. expect(view.model.get('omemo_supported')).toBe(false);
  456. expect(view.el.querySelector('.chat-textarea').value).toBe('This message will be encrypted');
  457. done();
  458. }));
  459. it("can receive a PreKeySignalMessage",
  460. mock.initConverseWithPromises(
  461. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  462. async function (done, _converse) {
  463. _converse.NUM_PREKEYS = 5; // Restrict to 5, otherwise the resulting stanza is too large to easily test
  464. let view, sent_stanza;
  465. test_utils.createContacts(_converse, 'current', 1);
  466. _converse.emit('rosterContactsFetched');
  467. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  468. await test_utils.waitUntil(() => initializedOMEMO(_converse));
  469. const obj = await _converse.ChatBox.prototype.encryptMessage('This is an encrypted message from the contact');
  470. // XXX: Normally the key will be encrypted via libsignal.
  471. // However, we're mocking libsignal in the tests, so we include
  472. // it as plaintext in the message.
  473. let stanza = $msg({
  474. 'from': contact_jid,
  475. 'to': _converse.connection.jid,
  476. 'type': 'chat',
  477. 'id': 'qwerty'
  478. }).c('body').t('This is a fallback message').up()
  479. .c('encrypted', {'xmlns': Strophe.NS.OMEMO})
  480. .c('header', {'sid': '555'})
  481. .c('key', {
  482. 'prekey': 'true',
  483. 'rid': _converse.omemo_store.get('device_id')
  484. }).t(u.arrayBufferToBase64(obj.key_and_tag)).up()
  485. .c('iv').t(obj.iv)
  486. .up().up()
  487. .c('payload').t(obj.payload);
  488. const generateMissingPreKeys = _converse.omemo_store.generateMissingPreKeys;
  489. spyOn(_converse.omemo_store, 'generateMissingPreKeys').and.callFake(() => {
  490. // Since it's difficult to override
  491. // decryptPreKeyWhisperMessage, where a prekey will be
  492. // removed from the store, we do it here, before the
  493. // missing prekeys are generated.
  494. _converse.omemo_store.removePreKey(1);
  495. return generateMissingPreKeys.apply(_converse.omemo_store, arguments);
  496. });
  497. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  498. let iq_stanza = await test_utils.waitUntil(() => _converse.chatboxviews.get(contact_jid));
  499. iq_stanza = await deviceListFetched(_converse, contact_jid);
  500. stanza = $iq({
  501. 'from': contact_jid,
  502. 'id': iq_stanza.nodeTree.getAttribute('id'),
  503. 'to': _converse.connection.jid,
  504. 'type': 'result',
  505. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  506. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  507. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  508. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  509. .c('device', {'id': '555'});
  510. // XXX: the bundle gets published twice, we want to make sure
  511. // that we wait for the 2nd, so we clear all the already sent
  512. // stanzas.
  513. _converse.connection.IQ_stanzas = [];
  514. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  515. await test_utils.waitUntil(() => _converse.omemo_store);
  516. iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
  517. expect(iq_stanza.toLocaleString()).toBe(
  518. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" type="set" xmlns="jabber:client">`+
  519. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  520. `<publish node="eu.siacs.conversations.axolotl.bundles:123456789">`+
  521. `<item>`+
  522. `<bundle xmlns="eu.siacs.conversations.axolotl">`+
  523. `<signedPreKeyPublic signedPreKeyId="0">${btoa("1234")}</signedPreKeyPublic>`+
  524. `<signedPreKeySignature>${btoa("11112222333344445555")}</signedPreKeySignature>`+
  525. `<identityKey>${btoa("1234")}</identityKey>`+
  526. `<prekeys>`+
  527. `<preKeyPublic preKeyId="0">${btoa("1234")}</preKeyPublic>`+
  528. `<preKeyPublic preKeyId="1">${btoa("1234")}</preKeyPublic>`+
  529. `<preKeyPublic preKeyId="2">${btoa("1234")}</preKeyPublic>`+
  530. `<preKeyPublic preKeyId="3">${btoa("1234")}</preKeyPublic>`+
  531. `<preKeyPublic preKeyId="4">${btoa("1234")}</preKeyPublic>`+
  532. `</prekeys>`+
  533. `</bundle>`+
  534. `</item>`+
  535. `</publish>`+
  536. `<publish-options>`+
  537. `<x type="submit" xmlns="jabber:x:data">`+
  538. `<field type="hidden" var="FORM_TYPE">`+
  539. `<value>http://jabber.org/protocol/pubsub#publish-options</value>`+
  540. `</field>`+
  541. `<field var="pubsub#access_model">`+
  542. `<value>open</value>`+
  543. `</field>`+
  544. `</x>`+
  545. `</publish-options>`+
  546. `</pubsub>`+
  547. `</iq>`)
  548. const own_device = _converse.devicelists.get(_converse.bare_jid).devices.get(_converse.omemo_store.get('device_id'));
  549. expect(own_device.get('bundle').prekeys.length).toBe(5);
  550. expect(_converse.omemo_store.generateMissingPreKeys).toHaveBeenCalled();
  551. done();
  552. }));
  553. it("updates device lists based on PEP messages",
  554. mock.initConverseWithPromises(
  555. null, ['rosterGroupsFetched'], {'allow_non_roster_messaging': true},
  556. async function (done, _converse) {
  557. await test_utils.waitUntilDiscoConfirmed(
  558. _converse, _converse.bare_jid,
  559. [{'category': 'pubsub', 'type': 'pep'}],
  560. ['http://jabber.org/protocol/pubsub#publish-options']
  561. );
  562. test_utils.createContacts(_converse, 'current', 1);
  563. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  564. // Wait until own devices are fetched
  565. let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
  566. expect(iq_stanza.toLocaleString()).toBe(
  567. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="dummy@localhost" type="get" xmlns="jabber:client">`+
  568. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  569. `<items node="eu.siacs.conversations.axolotl.devicelist"/>`+
  570. `</pubsub>`+
  571. `</iq>`);
  572. let stanza = $iq({
  573. 'from': _converse.bare_jid,
  574. 'id': iq_stanza.nodeTree.getAttribute('id'),
  575. 'to': _converse.bare_jid,
  576. 'type': 'result',
  577. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  578. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  579. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  580. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  581. .c('device', {'id': '555'});
  582. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  583. await test_utils.waitUntil(() => _converse.omemo_store);
  584. expect(_converse.chatboxes.length).toBe(1);
  585. expect(_converse.devicelists.length).toBe(1);
  586. const devicelist = _converse.devicelists.get(_converse.bare_jid);
  587. expect(devicelist.devices.length).toBe(2);
  588. expect(devicelist.devices.at(0).get('id')).toBe('555');
  589. expect(devicelist.devices.at(1).get('id')).toBe('123456789');
  590. iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
  591. stanza = $iq({
  592. 'from': _converse.bare_jid,
  593. 'id': iq_stanza.nodeTree.getAttribute('id'),
  594. 'to': _converse.bare_jid,
  595. 'type': 'result'});
  596. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  597. iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
  598. stanza = $iq({
  599. 'from': _converse.bare_jid,
  600. 'id': iq_stanza.nodeTree.getAttribute('id'),
  601. 'to': _converse.bare_jid,
  602. 'type': 'result'});
  603. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  604. await _converse.api.waitUntil('OMEMOInitialized');
  605. stanza = $msg({
  606. 'from': contact_jid,
  607. 'to': _converse.bare_jid,
  608. 'type': 'headline',
  609. 'id': 'update_01',
  610. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  611. .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
  612. .c('item')
  613. .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
  614. .c('device', {'id': '1234'})
  615. .c('device', {'id': '4223'})
  616. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  617. expect(_converse.devicelists.length).toBe(2);
  618. let devices = _converse.devicelists.get(contact_jid).devices;
  619. expect(devices.length).toBe(2);
  620. expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('1234,4223');
  621. stanza = $msg({
  622. 'from': contact_jid,
  623. 'to': _converse.bare_jid,
  624. 'type': 'headline',
  625. 'id': 'update_02',
  626. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  627. .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
  628. .c('item')
  629. .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
  630. .c('device', {'id': '4223'})
  631. .c('device', {'id': '4224'})
  632. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  633. expect(_converse.devicelists.length).toBe(2);
  634. expect(devices.length).toBe(2);
  635. expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('4223,4224');
  636. // Check that own devicelist gets updated
  637. stanza = $msg({
  638. 'from': _converse.bare_jid,
  639. 'to': _converse.bare_jid,
  640. 'type': 'headline',
  641. 'id': 'update_03',
  642. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  643. .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
  644. .c('item')
  645. .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
  646. .c('device', {'id': '123456789'})
  647. .c('device', {'id': '555'})
  648. .c('device', {'id': '777'})
  649. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  650. expect(_converse.devicelists.length).toBe(2);
  651. devices = _converse.devicelists.get(_converse.bare_jid).devices;
  652. expect(devices.length).toBe(3);
  653. expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,555,777');
  654. _converse.connection.IQ_stanzas = [];
  655. // Check that own device gets re-added
  656. stanza = $msg({
  657. 'from': _converse.bare_jid,
  658. 'to': _converse.bare_jid,
  659. 'type': 'headline',
  660. 'id': 'update_04',
  661. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  662. .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
  663. .c('item')
  664. .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
  665. .c('device', {'id': '444'})
  666. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  667. iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
  668. // Check that our own device is added again, but that removed
  669. // devices are not added.
  670. expect(iq_stanza.toLocaleString()).toBe(
  671. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute(`id`)}" type="set" xmlns="jabber:client">`+
  672. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  673. `<publish node="eu.siacs.conversations.axolotl.devicelist">`+
  674. `<item>`+
  675. `<list xmlns="eu.siacs.conversations.axolotl">`+
  676. `<device id="123456789"/>`+
  677. `<device id="444"/>`+
  678. `</list>`+
  679. `</item>`+
  680. `</publish>`+
  681. `<publish-options>`+
  682. `<x type="submit" xmlns="jabber:x:data">`+
  683. `<field type="hidden" var="FORM_TYPE">`+
  684. `<value>http://jabber.org/protocol/pubsub#publish-options</value>`+
  685. `</field>`+
  686. `<field var="pubsub#access_model">`+
  687. `<value>open</value>`+
  688. `</field>`+
  689. `</x>`+
  690. `</publish-options>`+
  691. `</pubsub>`+
  692. `</iq>`);
  693. expect(_converse.devicelists.length).toBe(2);
  694. devices = _converse.devicelists.get(_converse.bare_jid).devices;
  695. // The device id for this device (123456789) was also generated and added to the list,
  696. // which is why we have 2 devices now.
  697. expect(devices.length).toBe(2);
  698. expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,444');
  699. done();
  700. }));
  701. it("updates device bundles based on PEP messages",
  702. mock.initConverseWithPromises(
  703. null, ['rosterGroupsFetched'], {},
  704. async function (done, _converse) {
  705. await test_utils.waitUntilDiscoConfirmed(
  706. _converse, _converse.bare_jid,
  707. [{'category': 'pubsub', 'type': 'pep'}],
  708. ['http://jabber.org/protocol/pubsub#publish-options']
  709. );
  710. test_utils.createContacts(_converse, 'current');
  711. const contact_jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@localhost';
  712. let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
  713. expect(iq_stanza.toLocaleString()).toBe(
  714. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="dummy@localhost" type="get" xmlns="jabber:client">`+
  715. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  716. `<items node="eu.siacs.conversations.axolotl.devicelist"/>`+
  717. `</pubsub>`+
  718. `</iq>`);
  719. let stanza = $iq({
  720. 'from': contact_jid,
  721. 'id': iq_stanza.nodeTree.getAttribute('id'),
  722. 'to': _converse.bare_jid,
  723. 'type': 'result',
  724. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  725. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  726. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  727. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  728. .c('device', {'id': '555'});
  729. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  730. await await test_utils.waitUntil(() => _converse.omemo_store);
  731. expect(_converse.devicelists.length).toBe(1);
  732. let devicelist = _converse.devicelists.get(_converse.bare_jid);
  733. expect(devicelist.devices.length).toBe(2);
  734. expect(devicelist.devices.at(0).get('id')).toBe('555');
  735. expect(devicelist.devices.at(1).get('id')).toBe('123456789');
  736. iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
  737. stanza = $iq({
  738. 'from': _converse.bare_jid,
  739. 'id': iq_stanza.nodeTree.getAttribute('id'),
  740. 'to': _converse.bare_jid,
  741. 'type': 'result'});
  742. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  743. iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
  744. stanza = $iq({
  745. 'from': _converse.bare_jid,
  746. 'id': iq_stanza.nodeTree.getAttribute('id'),
  747. 'to': _converse.bare_jid,
  748. 'type': 'result'});
  749. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  750. await _converse.api.waitUntil('OMEMOInitialized');
  751. stanza = $msg({
  752. 'from': contact_jid,
  753. 'to': _converse.bare_jid,
  754. 'type': 'headline',
  755. 'id': 'update_01',
  756. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  757. .c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:555'})
  758. .c('item')
  759. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  760. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t('1111').up()
  761. .c('signedPreKeySignature').t('2222').up()
  762. .c('identityKey').t('3333').up()
  763. .c('prekeys')
  764. .c('preKeyPublic', {'preKeyId': '1001'}).up()
  765. .c('preKeyPublic', {'preKeyId': '1002'}).up()
  766. .c('preKeyPublic', {'preKeyId': '1003'});
  767. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  768. expect(_converse.devicelists.length).toBe(2);
  769. devicelist = _converse.devicelists.get(contact_jid);
  770. expect(devicelist.devices.length).toBe(1);
  771. let device = devicelist.devices.at(0);
  772. expect(device.get('bundle').identity_key).toBe('3333');
  773. expect(device.get('bundle').signed_prekey.public_key).toBe('1111');
  774. expect(device.get('bundle').signed_prekey.id).toBe(4223);
  775. expect(device.get('bundle').signed_prekey.signature).toBe('2222');
  776. expect(device.get('bundle').prekeys.length).toBe(3);
  777. expect(device.get('bundle').prekeys[0].id).toBe(1001);
  778. expect(device.get('bundle').prekeys[1].id).toBe(1002);
  779. expect(device.get('bundle').prekeys[2].id).toBe(1003);
  780. stanza = $msg({
  781. 'from': contact_jid,
  782. 'to': _converse.bare_jid,
  783. 'type': 'headline',
  784. 'id': 'update_02',
  785. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  786. .c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:555'})
  787. .c('item')
  788. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  789. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t('5555').up()
  790. .c('signedPreKeySignature').t('6666').up()
  791. .c('identityKey').t('7777').up()
  792. .c('prekeys')
  793. .c('preKeyPublic', {'preKeyId': '2001'}).up()
  794. .c('preKeyPublic', {'preKeyId': '2002'}).up()
  795. .c('preKeyPublic', {'preKeyId': '2003'});
  796. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  797. expect(_converse.devicelists.length).toBe(2);
  798. devicelist = _converse.devicelists.get(contact_jid);
  799. expect(devicelist.devices.length).toBe(1);
  800. device = devicelist.devices.at(0);
  801. expect(device.get('bundle').identity_key).toBe('7777');
  802. expect(device.get('bundle').signed_prekey.public_key).toBe('5555');
  803. expect(device.get('bundle').signed_prekey.id).toBe(4223);
  804. expect(device.get('bundle').signed_prekey.signature).toBe('6666');
  805. expect(device.get('bundle').prekeys.length).toBe(3);
  806. expect(device.get('bundle').prekeys[0].id).toBe(2001);
  807. expect(device.get('bundle').prekeys[1].id).toBe(2002);
  808. expect(device.get('bundle').prekeys[2].id).toBe(2003);
  809. stanza = $msg({
  810. 'from': _converse.bare_jid,
  811. 'to': _converse.bare_jid,
  812. 'type': 'headline',
  813. 'id': 'update_03',
  814. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  815. .c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:123456789'})
  816. .c('item')
  817. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  818. .c('signedPreKeyPublic', {'signedPreKeyId': '9999'}).t('8888').up()
  819. .c('signedPreKeySignature').t('3333').up()
  820. .c('identityKey').t('1111').up()
  821. .c('prekeys')
  822. .c('preKeyPublic', {'preKeyId': '3001'}).up()
  823. .c('preKeyPublic', {'preKeyId': '3002'}).up()
  824. .c('preKeyPublic', {'preKeyId': '3003'});
  825. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  826. expect(_converse.devicelists.length).toBe(2);
  827. devicelist = _converse.devicelists.get(_converse.bare_jid);
  828. expect(devicelist.devices.length).toBe(2);
  829. expect(devicelist.devices.at(0).get('id')).toBe('555');
  830. expect(devicelist.devices.at(1).get('id')).toBe('123456789');
  831. device = devicelist.devices.at(1);
  832. expect(device.get('bundle').identity_key).toBe('1111');
  833. expect(device.get('bundle').signed_prekey.public_key).toBe('8888');
  834. expect(device.get('bundle').signed_prekey.id).toBe(9999);
  835. expect(device.get('bundle').signed_prekey.signature).toBe('3333');
  836. expect(device.get('bundle').prekeys.length).toBe(3);
  837. expect(device.get('bundle').prekeys[0].id).toBe(3001);
  838. expect(device.get('bundle').prekeys[1].id).toBe(3002);
  839. expect(device.get('bundle').prekeys[2].id).toBe(3003);
  840. done();
  841. }));
  842. it("publishes a bundle with which an encrypted session can be created",
  843. mock.initConverseWithPromises(
  844. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  845. async function (done, _converse) {
  846. await test_utils.waitUntilDiscoConfirmed(
  847. _converse, _converse.bare_jid,
  848. [{'category': 'pubsub', 'type': 'pep'}],
  849. ['http://jabber.org/protocol/pubsub#publish-options']
  850. );
  851. _converse.NUM_PREKEYS = 2; // Restrict to 2, otherwise the resulting stanza is too large to easily test
  852. test_utils.createContacts(_converse, 'current', 1);
  853. _converse.emit('rosterContactsFetched');
  854. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  855. let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
  856. let stanza = $iq({
  857. 'from': contact_jid,
  858. 'id': iq_stanza.nodeTree.getAttribute('id'),
  859. 'to': _converse.bare_jid,
  860. 'type': 'result',
  861. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  862. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  863. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  864. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  865. .c('device', {'id': '482886413b977930064a5888b92134fe'});
  866. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  867. expect(_converse.devicelists.length).toBe(1);
  868. await test_utils.openChatBoxFor(_converse, contact_jid);
  869. iq_stanza = await ownDeviceHasBeenPublished(_converse);
  870. stanza = $iq({
  871. 'from': _converse.bare_jid,
  872. 'id': iq_stanza.nodeTree.getAttribute('id'),
  873. 'to': _converse.bare_jid,
  874. 'type': 'result'});
  875. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  876. iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
  877. expect(iq_stanza.toLocaleString()).toBe(
  878. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" type="set" xmlns="jabber:client">`+
  879. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  880. `<publish node="eu.siacs.conversations.axolotl.bundles:123456789">`+
  881. `<item>`+
  882. `<bundle xmlns="eu.siacs.conversations.axolotl">`+
  883. `<signedPreKeyPublic signedPreKeyId="0">${btoa("1234")}</signedPreKeyPublic>`+
  884. `<signedPreKeySignature>${btoa("11112222333344445555")}</signedPreKeySignature>`+
  885. `<identityKey>${btoa("1234")}</identityKey>`+
  886. `<prekeys>`+
  887. `<preKeyPublic preKeyId="0">${btoa("1234")}</preKeyPublic>`+
  888. `<preKeyPublic preKeyId="1">${btoa("1234")}</preKeyPublic>`+
  889. `</prekeys>`+
  890. `</bundle>`+
  891. `</item>`+
  892. `</publish>`+
  893. `<publish-options>`+
  894. `<x type="submit" xmlns="jabber:x:data">`+
  895. `<field type="hidden" var="FORM_TYPE">`+
  896. `<value>http://jabber.org/protocol/pubsub#publish-options</value>`+
  897. `</field>`+
  898. `<field var="pubsub#access_model">`+
  899. `<value>open</value>`+
  900. `</field>`+
  901. `</x>`+
  902. `</publish-options>`+
  903. `</pubsub>`+
  904. `</iq>`)
  905. stanza = $iq({
  906. 'from': _converse.bare_jid,
  907. 'id': iq_stanza.nodeTree.getAttribute('id'),
  908. 'to': _converse.bare_jid,
  909. 'type': 'result'});
  910. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  911. await _converse.api.waitUntil('OMEMOInitialized');
  912. done();
  913. }));
  914. it("adds a toolbar button for starting an encrypted chat session",
  915. mock.initConverseWithPromises(
  916. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  917. async function (done, _converse) {
  918. await test_utils.waitUntilDiscoConfirmed(
  919. _converse, _converse.bare_jid,
  920. [{'category': 'pubsub', 'type': 'pep'}],
  921. ['http://jabber.org/protocol/pubsub#publish-options']
  922. );
  923. test_utils.createContacts(_converse, 'current', 1);
  924. _converse.emit('rosterContactsFetched');
  925. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  926. let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
  927. expect(iq_stanza.toLocaleString()).toBe(
  928. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="dummy@localhost" type="get" xmlns="jabber:client">`+
  929. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  930. `<items node="eu.siacs.conversations.axolotl.devicelist"/>`+
  931. `</pubsub>`+
  932. `</iq>`);
  933. let stanza = $iq({
  934. 'from': _converse.bare_jid,
  935. 'id': iq_stanza.nodeTree.getAttribute('id'),
  936. 'to': _converse.bare_jid,
  937. 'type': 'result',
  938. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  939. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  940. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  941. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  942. .c('device', {'id': '482886413b977930064a5888b92134fe'});
  943. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  944. await test_utils.waitUntil(() => _converse.omemo_store);
  945. expect(_converse.devicelists.length).toBe(1);
  946. let devicelist = _converse.devicelists.get(_converse.bare_jid);
  947. expect(devicelist.devices.length).toBe(2);
  948. expect(devicelist.devices.at(0).get('id')).toBe('482886413b977930064a5888b92134fe');
  949. expect(devicelist.devices.at(1).get('id')).toBe('123456789');
  950. // Check that own device was published
  951. iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
  952. expect(iq_stanza.toLocaleString()).toBe(
  953. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute(`id`)}" type="set" xmlns="jabber:client">`+
  954. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  955. `<publish node="eu.siacs.conversations.axolotl.devicelist">`+
  956. `<item>`+
  957. `<list xmlns="eu.siacs.conversations.axolotl">`+
  958. `<device id="482886413b977930064a5888b92134fe"/>`+
  959. `<device id="123456789"/>`+
  960. `</list>`+
  961. `</item>`+
  962. `</publish>`+
  963. `<publish-options>`+
  964. `<x type="submit" xmlns="jabber:x:data">`+
  965. `<field type="hidden" var="FORM_TYPE">`+
  966. `<value>http://jabber.org/protocol/pubsub#publish-options</value>`+
  967. `</field>`+
  968. `<field var="pubsub#access_model">`+
  969. `<value>open</value>`+
  970. `</field>`+
  971. `</x>`+
  972. `</publish-options>`+
  973. `</pubsub>`+
  974. `</iq>`);
  975. stanza = $iq({
  976. 'from': _converse.bare_jid,
  977. 'id': iq_stanza.nodeTree.getAttribute('id'),
  978. 'to': _converse.bare_jid,
  979. 'type': 'result'});
  980. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  981. const iq_el = await test_utils.waitUntil(() => _.get(bundleHasBeenPublished(_converse), 'nodeTree'));
  982. expect(iq_el.getAttributeNames().sort().join()).toBe(["from", "type", "xmlns", "id"].sort().join());
  983. expect(iq_el.querySelector('prekeys').childNodes.length).toBe(100);
  984. const signed_prekeys = iq_el.querySelectorAll('signedPreKeyPublic');
  985. expect(signed_prekeys.length).toBe(1);
  986. const signed_prekey = signed_prekeys[0];
  987. expect(signed_prekey.getAttribute('signedPreKeyId')).toBe('0')
  988. expect(iq_el.querySelectorAll('signedPreKeySignature').length).toBe(1);
  989. expect(iq_el.querySelectorAll('identityKey').length).toBe(1);
  990. stanza = $iq({
  991. 'from': _converse.bare_jid,
  992. 'id': iq_el.getAttribute('id'),
  993. 'to': _converse.bare_jid,
  994. 'type': 'result'});
  995. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  996. await _converse.api.waitUntil('OMEMOInitialized', 1000);
  997. await test_utils.openChatBoxFor(_converse, contact_jid);
  998. iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
  999. expect(iq_stanza.toLocaleString()).toBe(
  1000. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
  1001. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  1002. `<items node="eu.siacs.conversations.axolotl.devicelist"/>`+
  1003. `</pubsub>`+
  1004. `</iq>`);
  1005. stanza = $iq({
  1006. 'from': contact_jid,
  1007. 'id': iq_stanza.nodeTree.getAttribute('id'),
  1008. 'to': _converse.bare_jid,
  1009. 'type': 'result',
  1010. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  1011. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  1012. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  1013. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  1014. .c('device', {'id': '368866411b877c30064a5f62b917cffe'}).up()
  1015. .c('device', {'id': '3300659945416e274474e469a1f0154c'}).up()
  1016. .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
  1017. .c('device', {'id': 'ae890ac52d0df67ed7cfdf51b644e901'});
  1018. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1019. devicelist = _converse.devicelists.get(contact_jid);
  1020. await test_utils.waitUntil(() => devicelist.devices.length);
  1021. expect(_converse.devicelists.length).toBe(2);
  1022. devicelist = _converse.devicelists.get(contact_jid);
  1023. expect(devicelist.devices.length).toBe(4);
  1024. expect(devicelist.devices.at(0).get('id')).toBe('368866411b877c30064a5f62b917cffe');
  1025. expect(devicelist.devices.at(1).get('id')).toBe('3300659945416e274474e469a1f0154c');
  1026. expect(devicelist.devices.at(2).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
  1027. expect(devicelist.devices.at(3).get('id')).toBe('ae890ac52d0df67ed7cfdf51b644e901');
  1028. await test_utils.waitUntil(() => _converse.chatboxviews.get(contact_jid).el.querySelector('.chat-toolbar'));
  1029. const view = _converse.chatboxviews.get(contact_jid);
  1030. const toolbar = view.el.querySelector('.chat-toolbar');
  1031. expect(view.model.get('omemo_active')).toBe(undefined);
  1032. let toggle = toolbar.querySelector('.toggle-omemo');
  1033. expect(_.isNull(toggle)).toBe(false);
  1034. expect(u.hasClass('fa-unlock', toggle)).toBe(true);
  1035. expect(u.hasClass('fa-lock', toggle)).toBe(false);
  1036. spyOn(view, 'toggleOMEMO').and.callThrough();
  1037. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  1038. toolbar.querySelector('.toggle-omemo').click();
  1039. expect(view.toggleOMEMO).toHaveBeenCalled();
  1040. expect(view.model.get('omemo_active')).toBe(true);
  1041. await test_utils.waitUntil(() => u.hasClass('fa-lock', toolbar.querySelector('.toggle-omemo')));
  1042. toggle = toolbar.querySelector('.toggle-omemo');
  1043. expect(u.hasClass('fa-unlock', toggle)).toBe(false);
  1044. expect(u.hasClass('fa-lock', toggle)).toBe(true);
  1045. const textarea = view.el.querySelector('.chat-textarea');
  1046. textarea.value = 'This message will be sent encrypted';
  1047. view.keyPressed({
  1048. target: textarea,
  1049. preventDefault: _.noop,
  1050. keyCode: 13
  1051. });
  1052. view.model.save({'omemo_supported': false});
  1053. toggle = toolbar.querySelector('.toggle-omemo');
  1054. expect(u.hasClass('fa-lock', toggle)).toBe(false);
  1055. expect(u.hasClass('fa-unlock', toggle)).toBe(true);
  1056. expect(u.hasClass('disabled', toggle)).toBe(true);
  1057. view.model.save({'omemo_supported': true});
  1058. toggle = toolbar.querySelector('.toggle-omemo');
  1059. expect(u.hasClass('fa-lock', toggle)).toBe(false);
  1060. expect(u.hasClass('fa-unlock', toggle)).toBe(true);
  1061. expect(u.hasClass('disabled', toggle)).toBe(false);
  1062. done();
  1063. }));
  1064. it("adds a toolbar button for starting an encrypted groupchat session",
  1065. mock.initConverseWithPromises(
  1066. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {'view_mode': 'fullscreen'},
  1067. async function (done, _converse) {
  1068. await test_utils.waitUntilDiscoConfirmed(
  1069. _converse, _converse.bare_jid,
  1070. [{'category': 'pubsub', 'type': 'pep'}],
  1071. ['http://jabber.org/protocol/pubsub#publish-options']
  1072. );
  1073. // MEMO encryption works only in members-only conferences that are non-anonymous.
  1074. const features = [
  1075. 'http://jabber.org/protocol/muc',
  1076. 'jabber:iq:register',
  1077. 'muc_passwordprotected',
  1078. 'muc_hidden',
  1079. 'muc_temporary',
  1080. 'muc_membersonly',
  1081. 'muc_unmoderated',
  1082. 'muc_nonanonymous'
  1083. ];
  1084. await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy', features);
  1085. const view = _converse.chatboxviews.get('lounge@localhost');
  1086. await test_utils.waitUntil(() => initializedOMEMO(_converse));
  1087. const toolbar = view.el.querySelector('.chat-toolbar');
  1088. let toggle = toolbar.querySelector('.toggle-omemo');
  1089. expect(view.model.get('omemo_active')).toBe(undefined);
  1090. expect(_.isNull(toggle)).toBe(false);
  1091. expect(u.hasClass('fa-unlock', toggle)).toBe(true);
  1092. expect(u.hasClass('fa-lock', toggle)).toBe(false);
  1093. expect(u.hasClass('disabled', toggle)).toBe(false);
  1094. expect(view.model.get('omemo_supported')).toBe(true);
  1095. toggle.click();
  1096. toggle = toolbar.querySelector('.toggle-omemo');
  1097. expect(view.model.get('omemo_active')).toBe(true);
  1098. expect(u.hasClass('fa-unlock', toggle)).toBe(false);
  1099. expect(u.hasClass('fa-lock', toggle)).toBe(true);
  1100. expect(u.hasClass('disabled', toggle)).toBe(false);
  1101. expect(view.model.get('omemo_supported')).toBe(true);
  1102. let contact_jid = 'newguy@localhost';
  1103. let stanza = $pres({
  1104. to: 'dummy@localhost/resource',
  1105. from: 'lounge@localhost/newguy'
  1106. })
  1107. .c('x', {xmlns: Strophe.NS.MUC_USER})
  1108. .c('item', {
  1109. 'affiliation': 'none',
  1110. 'jid': 'newguy@localhost/_converse.js-290929789',
  1111. 'role': 'participant'
  1112. }).tree();
  1113. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1114. let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
  1115. expect(iq_stanza.toLocaleString()).toBe(
  1116. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
  1117. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  1118. `<items node="eu.siacs.conversations.axolotl.devicelist"/>`+
  1119. `</pubsub>`+
  1120. `</iq>`);
  1121. stanza = $iq({
  1122. 'from': contact_jid,
  1123. 'id': iq_stanza.nodeTree.getAttribute('id'),
  1124. 'to': _converse.bare_jid,
  1125. 'type': 'result',
  1126. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  1127. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  1128. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  1129. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  1130. .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
  1131. .c('device', {'id': 'ae890ac52d0df67ed7cfdf51b644e901'});
  1132. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1133. await test_utils.waitUntil(() => _converse.omemo_store);
  1134. expect(_converse.devicelists.length).toBe(2);
  1135. const devicelist = _converse.devicelists.get(contact_jid);
  1136. expect(devicelist.devices.length).toBe(2);
  1137. expect(devicelist.devices.at(0).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
  1138. expect(devicelist.devices.at(1).get('id')).toBe('ae890ac52d0df67ed7cfdf51b644e901');
  1139. expect(view.model.get('omemo_active')).toBe(true);
  1140. toggle = toolbar.querySelector('.toggle-omemo');
  1141. expect(_.isNull(toggle)).toBe(false);
  1142. expect(u.hasClass('fa-unlock', toggle)).toBe(false);
  1143. expect(u.hasClass('fa-lock', toggle)).toBe(true);
  1144. expect(u.hasClass('disabled', toggle)).toBe(false);
  1145. expect(view.model.get('omemo_supported')).toBe(true);
  1146. // Test that the button gets disabled when the room becomes
  1147. // anonymous or semi-anonymous
  1148. view.model.save({'nonanonymous': false, 'semianonymous': true});
  1149. await test_utils.waitUntil(() => !view.model.get('omemo_supported'));
  1150. toggle = toolbar.querySelector('.toggle-omemo');
  1151. expect(_.isNull(toggle)).toBe(true);
  1152. expect(view.model.get('omemo_supported')).toBe(false);
  1153. view.model.save({'nonanonymous': true, 'semianonymous': false});
  1154. await test_utils.waitUntil(() => view.model.get('omemo_supported'));
  1155. toggle = toolbar.querySelector('.toggle-omemo');
  1156. expect(_.isNull(toggle)).toBe(false);
  1157. expect(u.hasClass('fa-unlock', toggle)).toBe(true);
  1158. expect(u.hasClass('fa-lock', toggle)).toBe(false);
  1159. expect(u.hasClass('disabled', toggle)).toBe(false);
  1160. // Test that the button gets disabled when the room becomes open
  1161. view.model.save({'membersonly': false, 'open': true});
  1162. await test_utils.waitUntil(() => !view.model.get('omemo_supported'));
  1163. toggle = toolbar.querySelector('.toggle-omemo');
  1164. expect(_.isNull(toggle)).toBe(true);
  1165. view.model.save({'membersonly': true, 'open': false});
  1166. await test_utils.waitUntil(() => view.model.get('omemo_supported'));
  1167. toggle = toolbar.querySelector('.toggle-omemo');
  1168. expect(_.isNull(toggle)).toBe(false);
  1169. expect(u.hasClass('fa-unlock', toggle)).toBe(true);
  1170. expect(u.hasClass('fa-lock', toggle)).toBe(false);
  1171. expect(u.hasClass('disabled', toggle)).toBe(false);
  1172. expect(view.model.get('omemo_supported')).toBe(true);
  1173. expect(view.model.get('omemo_active')).toBe(false);
  1174. toggle.click();
  1175. expect(view.model.get('omemo_active')).toBe(true);
  1176. // Someone enters the room who doesn't have OMEMO support, while we
  1177. // have OMEMO activated...
  1178. contact_jid = 'oldguy@localhost';
  1179. stanza = $pres({
  1180. to: 'dummy@localhost/resource',
  1181. from: 'lounge@localhost/oldguy'
  1182. })
  1183. .c('x', {xmlns: Strophe.NS.MUC_USER})
  1184. .c('item', {
  1185. 'affiliation': 'none',
  1186. 'jid': `${contact_jid}/_converse.js-290929788`,
  1187. 'role': 'participant'
  1188. }).tree();
  1189. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1190. iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
  1191. expect(iq_stanza.toLocaleString()).toBe(
  1192. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
  1193. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  1194. `<items node="eu.siacs.conversations.axolotl.devicelist"/>`+
  1195. `</pubsub>`+
  1196. `</iq>`);
  1197. stanza = $iq({
  1198. 'from': contact_jid,
  1199. 'id': iq_stanza.nodeTree.getAttribute('id'),
  1200. 'to': _converse.bare_jid,
  1201. 'type': 'error'
  1202. }).c('error', {'type': 'cancel'})
  1203. .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
  1204. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1205. await test_utils.waitUntil(() => !view.model.get('omemo_supported'));
  1206. expect(view.el.querySelector('.chat-error').textContent).toBe(
  1207. "oldguy doesn't appear to have a client that supports OMEMO. "+
  1208. "Encrypted chat will no longer be possible in this grouchat."
  1209. );
  1210. toggle = toolbar.querySelector('.toggle-omemo');
  1211. expect(_.isNull(toggle)).toBe(false);
  1212. expect(u.hasClass('fa-unlock', toggle)).toBe(true);
  1213. expect(u.hasClass('fa-lock', toggle)).toBe(false);
  1214. expect(u.hasClass('disabled', toggle)).toBe(true);
  1215. expect( _converse.chatboxviews.el.querySelector('.modal-body p')).toBe(null);
  1216. toggle.click();
  1217. const msg = _converse.chatboxviews.el.querySelector('.modal-body p');
  1218. expect(msg.textContent).toBe(
  1219. 'Cannot use end-to-end encryption in this groupchat, '+
  1220. 'either the groupchat has some anonymity or not all participants support OMEMO.');
  1221. done();
  1222. }));
  1223. it("shows OMEMO device fingerprints in the user details modal",
  1224. mock.initConverseWithPromises(
  1225. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  1226. async function (done, _converse) {
  1227. await test_utils.waitUntilDiscoConfirmed(
  1228. _converse, _converse.bare_jid,
  1229. [{'category': 'pubsub', 'type': 'pep'}],
  1230. ['http://jabber.org/protocol/pubsub#publish-options']
  1231. );
  1232. test_utils.createContacts(_converse, 'current', 1);
  1233. _converse.emit('rosterContactsFetched');
  1234. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  1235. await test_utils.openChatBoxFor(_converse, contact_jid)
  1236. // We simply emit, to avoid doing all the setup work
  1237. _converse.emit('OMEMOInitialized');
  1238. const view = _converse.chatboxviews.get(contact_jid);
  1239. const show_modal_button = view.el.querySelector('.show-user-details-modal');
  1240. show_modal_button.click();
  1241. const modal = view.user_details_modal;
  1242. await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
  1243. let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
  1244. expect(iq_stanza.toLocaleString()).toBe(
  1245. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="max.frankfurter@localhost" type="get" xmlns="jabber:client">`+
  1246. `<pubsub xmlns="http://jabber.org/protocol/pubsub"><items node="eu.siacs.conversations.axolotl.devicelist"/></pubsub>`+
  1247. `</iq>`);
  1248. let stanza = $iq({
  1249. 'from': contact_jid,
  1250. 'id': iq_stanza.nodeTree.getAttribute('id'),
  1251. 'to': _converse.bare_jid,
  1252. 'type': 'result',
  1253. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  1254. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  1255. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  1256. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  1257. .c('device', {'id': '555'});
  1258. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1259. await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
  1260. iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '555'));
  1261. expect(iq_stanza.toLocaleString()).toBe(
  1262. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="max.frankfurter@localhost" type="get" xmlns="jabber:client">`+
  1263. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  1264. `<items node="eu.siacs.conversations.axolotl.bundles:555"/>`+
  1265. `</pubsub>`+
  1266. `</iq>`);
  1267. stanza = $iq({
  1268. 'from': contact_jid,
  1269. 'id': iq_stanza.nodeTree.getAttribute('id'),
  1270. 'to': _converse.bare_jid,
  1271. 'type': 'result',
  1272. }).c('pubsub', {
  1273. 'xmlns': 'http://jabber.org/protocol/pubsub'
  1274. }).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:555"})
  1275. .c('item')
  1276. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  1277. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('1111')).up()
  1278. .c('signedPreKeySignature').t(btoa('2222')).up()
  1279. .c('identityKey').t('BQmHEOHjsYm3w5M8VqxAtqJmLCi7CaxxsdZz6G0YpuMI').up()
  1280. .c('prekeys')
  1281. .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1001')).up()
  1282. .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up()
  1283. .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
  1284. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  1285. await test_utils.waitUntil(() => modal.el.querySelectorAll('.fingerprints .fingerprint').length);
  1286. expect(modal.el.querySelectorAll('.fingerprints .fingerprint').length).toBe(1);
  1287. const el = modal.el.querySelector('.fingerprints .fingerprint');
  1288. expect(el.textContent.trim()).toBe(
  1289. u.formatFingerprint(u.arrayBufferToHex(u.base64ToArrayBuffer('BQmHEOHjsYm3w5M8VqxAtqJmLCi7CaxxsdZz6G0YpuMI')))
  1290. );
  1291. expect(modal.el.querySelectorAll('input[type="radio"]').length).toBe(2);
  1292. const devicelist = _converse.devicelists.get(contact_jid);
  1293. expect(devicelist.devices.get('555').get('trusted')).toBe(0);
  1294. let trusted_radio = modal.el.querySelector('input[type="radio"][name="555"][value="1"]');
  1295. expect(trusted_radio.checked).toBe(true);
  1296. let untrusted_radio = modal.el.querySelector('input[type="radio"][name="555"][value="-1"]');
  1297. expect(untrusted_radio.checked).toBe(false);
  1298. // Test that the device can be set to untrusted
  1299. untrusted_radio.click();
  1300. trusted_radio = document.querySelector('input[type="radio"][name="555"][value="1"]');
  1301. expect(trusted_radio.hasAttribute('checked')).toBe(false);
  1302. expect(devicelist.devices.get('555').get('trusted')).toBe(-1);
  1303. untrusted_radio = document.querySelector('input[type="radio"][name="555"][value="-1"]');
  1304. expect(untrusted_radio.hasAttribute('checked')).toBe(true);
  1305. trusted_radio.click();
  1306. expect(devicelist.devices.get('555').get('trusted')).toBe(1);
  1307. done();
  1308. }));
  1309. });
  1310. describe("A chatbox with an active OMEMO session", function() {
  1311. it("will not show the spoiler toolbar button",
  1312. mock.initConverseWithPromises(
  1313. null, ['rosterGroupsFetched'], {},
  1314. function (done, _converse) {
  1315. // TODO
  1316. done()
  1317. }));
  1318. });
  1319. }));