omemo.js 84 KB

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