omemo.js 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897
  1. (function (root, factory) {
  2. define(["jasmine", "mock", "test-utils"], factory);
  3. } (this, function (jasmine, mock, test_utils) {
  4. var Strophe = converse.env.Strophe;
  5. var b64_sha1 = converse.env.b64_sha1;
  6. var $iq = converse.env.$iq;
  7. var $msg = converse.env.$msg;
  8. var _ = converse.env._;
  9. var u = converse.env.utils;
  10. function deviceListFetched (_converse, jid) {
  11. return _.filter(
  12. _converse.connection.IQ_stanzas,
  13. iq => iq.nodeTree.querySelector(`iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.devicelist"]`)
  14. ).pop();
  15. }
  16. function ownDeviceHasBeenPublished (_converse) {
  17. return _.filter(
  18. _converse.connection.IQ_stanzas,
  19. iq => iq.nodeTree.querySelector('iq[from="'+_converse.bare_jid+'"] publish[node="eu.siacs.conversations.axolotl.devicelist"]')
  20. ).pop();
  21. }
  22. function bundleHasBeenPublished (_converse) {
  23. return _.filter(
  24. _converse.connection.IQ_stanzas,
  25. iq => iq.nodeTree.querySelector('publish[node="eu.siacs.conversations.axolotl.bundles:123456789"]')
  26. ).pop();
  27. }
  28. function bundleFetched (_converse, jid, device_id) {
  29. return _.filter(
  30. _converse.connection.IQ_stanzas,
  31. (iq) => iq.nodeTree.querySelector(`iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.bundles:${device_id}"]`)
  32. ).pop();
  33. }
  34. async function initializedOMEMO (_converse) {
  35. let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
  36. let stanza = $iq({
  37. 'from': _converse.bare_jid,
  38. 'id': iq_stanza.nodeTree.getAttribute('id'),
  39. 'to': _converse.bare_jid,
  40. 'type': 'result',
  41. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  42. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  43. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  44. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  45. .c('device', {'id': '482886413b977930064a5888b92134fe'});
  46. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  47. iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse))
  48. stanza = $iq({
  49. 'from': _converse.bare_jid,
  50. 'id': iq_stanza.nodeTree.getAttribute('id'),
  51. 'to': _converse.bare_jid,
  52. 'type': 'result'});
  53. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  54. iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse))
  55. stanza = $iq({
  56. 'from': _converse.bare_jid,
  57. 'id': iq_stanza.nodeTree.getAttribute('id'),
  58. 'to': _converse.bare_jid,
  59. 'type': 'result'});
  60. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  61. await _converse.api.waitUntil('OMEMOInitialized');
  62. }
  63. describe("The OMEMO module", function() {
  64. it("adds methods for encrypting and decrypting messages via AES GCM",
  65. mock.initConverseWithPromises(
  66. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  67. async function (done, _converse) {
  68. const message = 'This message will be encrypted'
  69. test_utils.createContacts(_converse, 'current', 1);
  70. _converse.emit('rosterContactsFetched');
  71. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  72. const view = await test_utils.openChatBoxFor(_converse, contact_jid);
  73. const payload = await view.model.encryptMessage(message);
  74. const result = await view.model.decryptMessage(payload);
  75. expect(result).toBe(message);
  76. done();
  77. }));
  78. it("enables encrypted messages to be sent and received",
  79. mock.initConverseWithPromises(
  80. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  81. async function (done, _converse) {
  82. let sent_stanza;
  83. test_utils.createContacts(_converse, 'current', 1);
  84. _converse.emit('rosterContactsFetched');
  85. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  86. await test_utils.waitUntil(() => initializedOMEMO(_converse));
  87. await test_utils.openChatBoxFor(_converse, contact_jid);
  88. let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
  89. let stanza = $iq({
  90. 'from': contact_jid,
  91. 'id': iq_stanza.nodeTree.getAttribute('id'),
  92. 'to': _converse.connection.jid,
  93. 'type': 'result',
  94. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  95. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  96. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  97. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  98. .c('device', {'id': '555'});
  99. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  100. await test_utils.waitUntil(() => _converse.omemo_store);
  101. const devicelist = _converse.devicelists.get({'jid': contact_jid});
  102. expect(devicelist.devices.length).toBe(1);
  103. const view = _converse.chatboxviews.get(contact_jid);
  104. view.model.set('omemo_active', true);
  105. const textarea = view.el.querySelector('.chat-textarea');
  106. textarea.value = 'This message will be encrypted';
  107. view.keyPressed({
  108. target: textarea,
  109. preventDefault: _.noop,
  110. keyCode: 13 // Enter
  111. });
  112. iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '555'));
  113. stanza = $iq({
  114. 'from': contact_jid,
  115. 'id': iq_stanza.nodeTree.getAttribute('id'),
  116. 'to': _converse.bare_jid,
  117. 'type': 'result',
  118. }).c('pubsub', {
  119. 'xmlns': 'http://jabber.org/protocol/pubsub'
  120. }).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:555"})
  121. .c('item')
  122. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  123. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('1111')).up()
  124. .c('signedPreKeySignature').t(btoa('2222')).up()
  125. .c('identityKey').t(btoa('3333')).up()
  126. .c('prekeys')
  127. .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1001')).up()
  128. .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up()
  129. .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
  130. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  131. iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'));
  132. stanza = $iq({
  133. 'from': _converse.bare_jid,
  134. 'id': iq_stanza.nodeTree.getAttribute('id'),
  135. 'to': _converse.bare_jid,
  136. 'type': 'result',
  137. }).c('pubsub', {
  138. 'xmlns': 'http://jabber.org/protocol/pubsub'
  139. }).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:482886413b977930064a5888b92134fe"})
  140. .c('item')
  141. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  142. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('100000')).up()
  143. .c('signedPreKeySignature').t(btoa('200000')).up()
  144. .c('identityKey').t(btoa('300000')).up()
  145. .c('prekeys')
  146. .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1991')).up()
  147. .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1992')).up()
  148. .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1993'));
  149. spyOn(_converse.connection, 'send').and.callFake(stanza => { sent_stanza = stanza });
  150. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  151. await test_utils.waitUntil(() => sent_stanza);
  152. expect(sent_stanza.toLocaleString()).toBe(
  153. `<message from="dummy@localhost/resource" id="${sent_stanza.nodeTree.getAttribute("id")}" `+
  154. `to="max.frankfurter@localhost" `+
  155. `type="chat" xmlns="jabber:client">`+
  156. `<body>This is an OMEMO encrypted message which your client doesn’t seem to support. Find more information on https://conversations.im/omemo</body>`+
  157. `<encrypted xmlns="eu.siacs.conversations.axolotl">`+
  158. `<header sid="123456789">`+
  159. `<key rid="482886413b977930064a5888b92134fe">YzFwaDNSNzNYNw==</key>`+
  160. `<key rid="555">YzFwaDNSNzNYNw==</key>`+
  161. `<iv>${sent_stanza.nodeTree.querySelector("iv").textContent}</iv>`+
  162. `</header>`+
  163. `<payload>${sent_stanza.nodeTree.querySelector("payload").textContent}</payload>`+
  164. `</encrypted>`+
  165. `<store xmlns="urn:xmpp:hints"/>`+
  166. `</message>`);
  167. // Test reception of an encrypted message
  168. const obj = await view.model.encryptMessage('This is an encrypted message from the contact')
  169. // XXX: Normally the key will be encrypted via libsignal.
  170. // However, we're mocking libsignal in the tests, so we include
  171. // it as plaintext in the message.
  172. stanza = $msg({
  173. 'from': contact_jid,
  174. 'to': _converse.connection.jid,
  175. 'type': 'chat',
  176. 'id': 'qwerty'
  177. }).c('body').t('This is a fallback message').up()
  178. .c('encrypted', {'xmlns': Strophe.NS.OMEMO})
  179. .c('header', {'sid': '555'})
  180. .c('key', {'rid': _converse.omemo_store.get('device_id')}).t(u.arrayBufferToBase64(obj.key_and_tag)).up()
  181. .c('iv').t(obj.iv)
  182. .up().up()
  183. .c('payload').t(obj.payload);
  184. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  185. await test_utils.waitUntil(() => view.model.messages.length > 1);
  186. expect(view.model.messages.length).toBe(2);
  187. const last_msg = view.model.messages.at(1);
  188. expect(view.el.querySelectorAll('.chat-msg__body')[1].textContent.trim())
  189. .toBe('This is an encrypted message from the contact');
  190. done();
  191. }));
  192. it("can receive a PreKeySignalMessage",
  193. mock.initConverseWithPromises(
  194. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  195. async function (done, _converse) {
  196. _converse.NUM_PREKEYS = 5; // Restrict to 5, otherwise the resulting stanza is too large to easily test
  197. let view, sent_stanza;
  198. test_utils.createContacts(_converse, 'current', 1);
  199. _converse.emit('rosterContactsFetched');
  200. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  201. await test_utils.waitUntil(() => initializedOMEMO(_converse));
  202. const obj = await _converse.ChatBox.prototype.encryptMessage('This is an encrypted message from the contact');
  203. // XXX: Normally the key will be encrypted via libsignal.
  204. // However, we're mocking libsignal in the tests, so we include
  205. // it as plaintext in the message.
  206. let stanza = $msg({
  207. 'from': contact_jid,
  208. 'to': _converse.connection.jid,
  209. 'type': 'chat',
  210. 'id': 'qwerty'
  211. }).c('body').t('This is a fallback message').up()
  212. .c('encrypted', {'xmlns': Strophe.NS.OMEMO})
  213. .c('header', {'sid': '555'})
  214. .c('key', {
  215. 'prekey': 'true',
  216. 'rid': _converse.omemo_store.get('device_id')
  217. }).t(u.arrayBufferToBase64(obj.key_and_tag)).up()
  218. .c('iv').t(obj.iv)
  219. .up().up()
  220. .c('payload').t(obj.payload);
  221. const generateMissingPreKeys = _converse.omemo_store.generateMissingPreKeys;
  222. spyOn(_converse.omemo_store, 'generateMissingPreKeys').and.callFake(() => {
  223. // Since it's difficult to override
  224. // decryptPreKeyWhisperMessage, where a prekey will be
  225. // removed from the store, we do it here, before the
  226. // missing prekeys are generated.
  227. _converse.omemo_store.removePreKey(1);
  228. return generateMissingPreKeys.apply(_converse.omemo_store, arguments);
  229. });
  230. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  231. let iq_stanza = await test_utils.waitUntil(() => _converse.chatboxviews.get(contact_jid));
  232. iq_stanza = await deviceListFetched(_converse, contact_jid);
  233. stanza = $iq({
  234. 'from': contact_jid,
  235. 'id': iq_stanza.nodeTree.getAttribute('id'),
  236. 'to': _converse.connection.jid,
  237. 'type': 'result',
  238. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  239. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  240. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  241. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  242. .c('device', {'id': '555'});
  243. // XXX: the bundle gets published twice, we want to make sure
  244. // that we wait for the 2nd, so we clear all the already sent
  245. // stanzas.
  246. _converse.connection.IQ_stanzas = [];
  247. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  248. await test_utils.waitUntil(() => _converse.omemo_store);
  249. iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
  250. expect(iq_stanza.toLocaleString()).toBe(
  251. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" type="set" xmlns="jabber:client">`+
  252. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  253. `<publish node="eu.siacs.conversations.axolotl.bundles:123456789">`+
  254. `<item>`+
  255. `<bundle xmlns="eu.siacs.conversations.axolotl">`+
  256. `<signedPreKeyPublic signedPreKeyId="0">${btoa("1234")}</signedPreKeyPublic>`+
  257. `<signedPreKeySignature>${btoa("11112222333344445555")}</signedPreKeySignature>`+
  258. `<identityKey>${btoa("1234")}</identityKey>`+
  259. `<prekeys>`+
  260. `<preKeyPublic preKeyId="0">${btoa("1234")}</preKeyPublic>`+
  261. `<preKeyPublic preKeyId="1">${btoa("1234")}</preKeyPublic>`+
  262. `<preKeyPublic preKeyId="2">${btoa("1234")}</preKeyPublic>`+
  263. `<preKeyPublic preKeyId="3">${btoa("1234")}</preKeyPublic>`+
  264. `<preKeyPublic preKeyId="4">${btoa("1234")}</preKeyPublic>`+
  265. `</prekeys>`+
  266. `</bundle>`+
  267. `</item>`+
  268. `</publish>`+
  269. `</pubsub>`+
  270. `</iq>`)
  271. const own_device = _converse.devicelists.get(_converse.bare_jid).devices.get(_converse.omemo_store.get('device_id'));
  272. expect(own_device.get('bundle').prekeys.length).toBe(5);
  273. expect(_converse.omemo_store.generateMissingPreKeys).toHaveBeenCalled();
  274. done();
  275. }));
  276. it("updates device lists based on PEP messages",
  277. mock.initConverseWithPromises(
  278. null, ['rosterGroupsFetched'], {'allow_non_roster_messaging': true},
  279. async function (done, _converse) {
  280. test_utils.createContacts(_converse, 'current', 1);
  281. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  282. // Wait until own devices are fetched
  283. let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
  284. expect(iq_stanza.toLocaleString()).toBe(
  285. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="dummy@localhost" type="get" xmlns="jabber:client">`+
  286. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  287. `<items node="eu.siacs.conversations.axolotl.devicelist"/>`+
  288. `</pubsub>`+
  289. `</iq>`);
  290. let stanza = $iq({
  291. 'from': _converse.bare_jid,
  292. 'id': iq_stanza.nodeTree.getAttribute('id'),
  293. 'to': _converse.bare_jid,
  294. 'type': 'result',
  295. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  296. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  297. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  298. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  299. .c('device', {'id': '555'});
  300. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  301. await test_utils.waitUntil(() => _converse.omemo_store);
  302. expect(_converse.chatboxes.length).toBe(1);
  303. expect(_converse.devicelists.length).toBe(1);
  304. const devicelist = _converse.devicelists.get(_converse.bare_jid);
  305. expect(devicelist.devices.length).toBe(2);
  306. expect(devicelist.devices.at(0).get('id')).toBe('555');
  307. expect(devicelist.devices.at(1).get('id')).toBe('123456789');
  308. iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
  309. stanza = $iq({
  310. 'from': _converse.bare_jid,
  311. 'id': iq_stanza.nodeTree.getAttribute('id'),
  312. 'to': _converse.bare_jid,
  313. 'type': 'result'});
  314. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  315. iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
  316. stanza = $iq({
  317. 'from': _converse.bare_jid,
  318. 'id': iq_stanza.nodeTree.getAttribute('id'),
  319. 'to': _converse.bare_jid,
  320. 'type': 'result'});
  321. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  322. await _converse.api.waitUntil('OMEMOInitialized');
  323. stanza = $msg({
  324. 'from': contact_jid,
  325. 'to': _converse.bare_jid,
  326. 'type': 'headline',
  327. 'id': 'update_01',
  328. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  329. .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
  330. .c('item')
  331. .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
  332. .c('device', {'id': '1234'})
  333. .c('device', {'id': '4223'})
  334. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  335. expect(_converse.devicelists.length).toBe(2);
  336. let devices = _converse.devicelists.get(contact_jid).devices;
  337. expect(devices.length).toBe(2);
  338. expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('1234,4223');
  339. stanza = $msg({
  340. 'from': contact_jid,
  341. 'to': _converse.bare_jid,
  342. 'type': 'headline',
  343. 'id': 'update_02',
  344. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  345. .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
  346. .c('item')
  347. .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
  348. .c('device', {'id': '4223'})
  349. .c('device', {'id': '4224'})
  350. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  351. expect(_converse.devicelists.length).toBe(2);
  352. expect(devices.length).toBe(2);
  353. expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('4223,4224');
  354. // Check that own devicelist gets updated
  355. stanza = $msg({
  356. 'from': _converse.bare_jid,
  357. 'to': _converse.bare_jid,
  358. 'type': 'headline',
  359. 'id': 'update_03',
  360. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  361. .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
  362. .c('item')
  363. .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
  364. .c('device', {'id': '123456789'})
  365. .c('device', {'id': '555'})
  366. .c('device', {'id': '777'})
  367. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  368. expect(_converse.devicelists.length).toBe(2);
  369. devices = _converse.devicelists.get(_converse.bare_jid).devices;
  370. expect(devices.length).toBe(3);
  371. expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,555,777');
  372. _converse.connection.IQ_stanzas = [];
  373. // Check that own device gets re-added
  374. stanza = $msg({
  375. 'from': _converse.bare_jid,
  376. 'to': _converse.bare_jid,
  377. 'type': 'headline',
  378. 'id': 'update_04',
  379. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  380. .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
  381. .c('item')
  382. .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
  383. .c('device', {'id': '444'})
  384. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  385. iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
  386. // Check that our own device is added again, but that removed
  387. // devices are not added.
  388. expect(iq_stanza.toLocaleString()).toBe(
  389. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute(`id`)}" type="set" xmlns="jabber:client">`+
  390. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  391. `<publish node="eu.siacs.conversations.axolotl.devicelist">`+
  392. `<item>`+
  393. `<list xmlns="eu.siacs.conversations.axolotl">`+
  394. `<device id="123456789"/>`+
  395. `<device id="444"/>`+
  396. `</list>`+
  397. `</item>`+
  398. `</publish>`+
  399. `</pubsub>`+
  400. `</iq>`);
  401. expect(_converse.devicelists.length).toBe(2);
  402. devices = _converse.devicelists.get(_converse.bare_jid).devices;
  403. // The device id for this device (123456789) was also generated and added to the list,
  404. // which is why we have 2 devices now.
  405. expect(devices.length).toBe(2);
  406. expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,444');
  407. done();
  408. }));
  409. it("updates device bundles based on PEP messages",
  410. mock.initConverseWithPromises(
  411. null, ['rosterGroupsFetched'], {},
  412. async function (done, _converse) {
  413. test_utils.createContacts(_converse, 'current');
  414. const contact_jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@localhost';
  415. let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
  416. expect(iq_stanza.toLocaleString()).toBe(
  417. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="dummy@localhost" type="get" xmlns="jabber:client">`+
  418. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  419. `<items node="eu.siacs.conversations.axolotl.devicelist"/>`+
  420. `</pubsub>`+
  421. `</iq>`);
  422. let stanza = $iq({
  423. 'from': contact_jid,
  424. 'id': iq_stanza.nodeTree.getAttribute('id'),
  425. 'to': _converse.bare_jid,
  426. 'type': 'result',
  427. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  428. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  429. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  430. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  431. .c('device', {'id': '555'});
  432. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  433. await await test_utils.waitUntil(() => _converse.omemo_store);
  434. expect(_converse.devicelists.length).toBe(1);
  435. let devicelist = _converse.devicelists.get(_converse.bare_jid);
  436. expect(devicelist.devices.length).toBe(2);
  437. expect(devicelist.devices.at(0).get('id')).toBe('555');
  438. expect(devicelist.devices.at(1).get('id')).toBe('123456789');
  439. iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
  440. stanza = $iq({
  441. 'from': _converse.bare_jid,
  442. 'id': iq_stanza.nodeTree.getAttribute('id'),
  443. 'to': _converse.bare_jid,
  444. 'type': 'result'});
  445. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  446. iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
  447. stanza = $iq({
  448. 'from': _converse.bare_jid,
  449. 'id': iq_stanza.nodeTree.getAttribute('id'),
  450. 'to': _converse.bare_jid,
  451. 'type': 'result'});
  452. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  453. await _converse.api.waitUntil('OMEMOInitialized');
  454. stanza = $msg({
  455. 'from': contact_jid,
  456. 'to': _converse.bare_jid,
  457. 'type': 'headline',
  458. 'id': 'update_01',
  459. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  460. .c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:555'})
  461. .c('item')
  462. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  463. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t('1111').up()
  464. .c('signedPreKeySignature').t('2222').up()
  465. .c('identityKey').t('3333').up()
  466. .c('prekeys')
  467. .c('preKeyPublic', {'preKeyId': '1001'}).up()
  468. .c('preKeyPublic', {'preKeyId': '1002'}).up()
  469. .c('preKeyPublic', {'preKeyId': '1003'});
  470. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  471. expect(_converse.devicelists.length).toBe(2);
  472. devicelist = _converse.devicelists.get(contact_jid);
  473. expect(devicelist.devices.length).toBe(1);
  474. let device = devicelist.devices.at(0);
  475. expect(device.get('bundle').identity_key).toBe('3333');
  476. expect(device.get('bundle').signed_prekey.public_key).toBe('1111');
  477. expect(device.get('bundle').signed_prekey.id).toBe(4223);
  478. expect(device.get('bundle').signed_prekey.signature).toBe('2222');
  479. expect(device.get('bundle').prekeys.length).toBe(3);
  480. expect(device.get('bundle').prekeys[0].id).toBe(1001);
  481. expect(device.get('bundle').prekeys[1].id).toBe(1002);
  482. expect(device.get('bundle').prekeys[2].id).toBe(1003);
  483. stanza = $msg({
  484. 'from': contact_jid,
  485. 'to': _converse.bare_jid,
  486. 'type': 'headline',
  487. 'id': 'update_02',
  488. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  489. .c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:555'})
  490. .c('item')
  491. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  492. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t('5555').up()
  493. .c('signedPreKeySignature').t('6666').up()
  494. .c('identityKey').t('7777').up()
  495. .c('prekeys')
  496. .c('preKeyPublic', {'preKeyId': '2001'}).up()
  497. .c('preKeyPublic', {'preKeyId': '2002'}).up()
  498. .c('preKeyPublic', {'preKeyId': '2003'});
  499. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  500. expect(_converse.devicelists.length).toBe(2);
  501. devicelist = _converse.devicelists.get(contact_jid);
  502. expect(devicelist.devices.length).toBe(1);
  503. device = devicelist.devices.at(0);
  504. expect(device.get('bundle').identity_key).toBe('7777');
  505. expect(device.get('bundle').signed_prekey.public_key).toBe('5555');
  506. expect(device.get('bundle').signed_prekey.id).toBe(4223);
  507. expect(device.get('bundle').signed_prekey.signature).toBe('6666');
  508. expect(device.get('bundle').prekeys.length).toBe(3);
  509. expect(device.get('bundle').prekeys[0].id).toBe(2001);
  510. expect(device.get('bundle').prekeys[1].id).toBe(2002);
  511. expect(device.get('bundle').prekeys[2].id).toBe(2003);
  512. stanza = $msg({
  513. 'from': _converse.bare_jid,
  514. 'to': _converse.bare_jid,
  515. 'type': 'headline',
  516. 'id': 'update_03',
  517. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  518. .c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:123456789'})
  519. .c('item')
  520. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  521. .c('signedPreKeyPublic', {'signedPreKeyId': '9999'}).t('8888').up()
  522. .c('signedPreKeySignature').t('3333').up()
  523. .c('identityKey').t('1111').up()
  524. .c('prekeys')
  525. .c('preKeyPublic', {'preKeyId': '3001'}).up()
  526. .c('preKeyPublic', {'preKeyId': '3002'}).up()
  527. .c('preKeyPublic', {'preKeyId': '3003'});
  528. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  529. expect(_converse.devicelists.length).toBe(2);
  530. devicelist = _converse.devicelists.get(_converse.bare_jid);
  531. expect(devicelist.devices.length).toBe(2);
  532. expect(devicelist.devices.at(0).get('id')).toBe('555');
  533. expect(devicelist.devices.at(1).get('id')).toBe('123456789');
  534. device = devicelist.devices.at(1);
  535. expect(device.get('bundle').identity_key).toBe('1111');
  536. expect(device.get('bundle').signed_prekey.public_key).toBe('8888');
  537. expect(device.get('bundle').signed_prekey.id).toBe(9999);
  538. expect(device.get('bundle').signed_prekey.signature).toBe('3333');
  539. expect(device.get('bundle').prekeys.length).toBe(3);
  540. expect(device.get('bundle').prekeys[0].id).toBe(3001);
  541. expect(device.get('bundle').prekeys[1].id).toBe(3002);
  542. expect(device.get('bundle').prekeys[2].id).toBe(3003);
  543. done();
  544. }));
  545. it("publishes a bundle with which an encrypted session can be created",
  546. mock.initConverseWithPromises(
  547. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  548. async function (done, _converse) {
  549. _converse.NUM_PREKEYS = 2; // Restrict to 2, otherwise the resulting stanza is too large to easily test
  550. test_utils.createContacts(_converse, 'current', 1);
  551. _converse.emit('rosterContactsFetched');
  552. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  553. let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
  554. let stanza = $iq({
  555. 'from': contact_jid,
  556. 'id': iq_stanza.nodeTree.getAttribute('id'),
  557. 'to': _converse.bare_jid,
  558. 'type': 'result',
  559. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  560. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  561. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  562. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  563. .c('device', {'id': '482886413b977930064a5888b92134fe'});
  564. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  565. expect(_converse.devicelists.length).toBe(1);
  566. await test_utils.openChatBoxFor(_converse, contact_jid);
  567. iq_stanza = await ownDeviceHasBeenPublished(_converse);
  568. stanza = $iq({
  569. 'from': _converse.bare_jid,
  570. 'id': iq_stanza.nodeTree.getAttribute('id'),
  571. 'to': _converse.bare_jid,
  572. 'type': 'result'});
  573. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  574. iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
  575. expect(iq_stanza.toLocaleString()).toBe(
  576. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" type="set" xmlns="jabber:client">`+
  577. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  578. `<publish node="eu.siacs.conversations.axolotl.bundles:123456789">`+
  579. `<item>`+
  580. `<bundle xmlns="eu.siacs.conversations.axolotl">`+
  581. `<signedPreKeyPublic signedPreKeyId="0">${btoa("1234")}</signedPreKeyPublic>`+
  582. `<signedPreKeySignature>${btoa("11112222333344445555")}</signedPreKeySignature>`+
  583. `<identityKey>${btoa("1234")}</identityKey>`+
  584. `<prekeys>`+
  585. `<preKeyPublic preKeyId="0">${btoa("1234")}</preKeyPublic>`+
  586. `<preKeyPublic preKeyId="1">${btoa("1234")}</preKeyPublic>`+
  587. `</prekeys>`+
  588. `</bundle>`+
  589. `</item>`+
  590. `</publish>`+
  591. `</pubsub>`+
  592. `</iq>`)
  593. stanza = $iq({
  594. 'from': _converse.bare_jid,
  595. 'id': iq_stanza.nodeTree.getAttribute('id'),
  596. 'to': _converse.bare_jid,
  597. 'type': 'result'});
  598. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  599. await _converse.api.waitUntil('OMEMOInitialized');
  600. done();
  601. }));
  602. it("adds a toolbar button for starting an encrypted chat session",
  603. mock.initConverseWithPromises(
  604. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  605. async function (done, _converse) {
  606. test_utils.createContacts(_converse, 'current', 1);
  607. _converse.emit('rosterContactsFetched');
  608. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  609. let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
  610. expect(iq_stanza.toLocaleString()).toBe(
  611. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="dummy@localhost" type="get" xmlns="jabber:client">`+
  612. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  613. `<items node="eu.siacs.conversations.axolotl.devicelist"/>`+
  614. `</pubsub>`+
  615. `</iq>`);
  616. let stanza = $iq({
  617. 'from': _converse.bare_jid,
  618. 'id': iq_stanza.nodeTree.getAttribute('id'),
  619. 'to': _converse.bare_jid,
  620. 'type': 'result',
  621. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  622. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  623. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  624. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  625. .c('device', {'id': '482886413b977930064a5888b92134fe'});
  626. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  627. await test_utils.waitUntil(() => _converse.omemo_store);
  628. expect(_converse.devicelists.length).toBe(1);
  629. let devicelist = _converse.devicelists.get(_converse.bare_jid);
  630. expect(devicelist.devices.length).toBe(2);
  631. expect(devicelist.devices.at(0).get('id')).toBe('482886413b977930064a5888b92134fe');
  632. expect(devicelist.devices.at(1).get('id')).toBe('123456789');
  633. // Check that own device was published
  634. iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
  635. expect(iq_stanza.toLocaleString()).toBe(
  636. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute(`id`)}" type="set" xmlns="jabber:client">`+
  637. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  638. `<publish node="eu.siacs.conversations.axolotl.devicelist">`+
  639. `<item>`+
  640. `<list xmlns="eu.siacs.conversations.axolotl">`+
  641. `<device id="482886413b977930064a5888b92134fe"/>`+
  642. `<device id="123456789"/>`+
  643. `</list>`+
  644. `</item>`+
  645. `</publish>`+
  646. `</pubsub>`+
  647. `</iq>`);
  648. stanza = $iq({
  649. 'from': _converse.bare_jid,
  650. 'id': iq_stanza.nodeTree.getAttribute('id'),
  651. 'to': _converse.bare_jid,
  652. 'type': 'result'});
  653. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  654. const iq_el = await test_utils.waitUntil(() => _.get(bundleHasBeenPublished(_converse), 'nodeTree'));
  655. expect(iq_el.getAttributeNames().sort().join()).toBe(["from", "type", "xmlns", "id"].sort().join());
  656. expect(iq_el.querySelector('prekeys').childNodes.length).toBe(100);
  657. const signed_prekeys = iq_el.querySelectorAll('signedPreKeyPublic');
  658. expect(signed_prekeys.length).toBe(1);
  659. const signed_prekey = signed_prekeys[0];
  660. expect(signed_prekey.getAttribute('signedPreKeyId')).toBe('0')
  661. expect(iq_el.querySelectorAll('signedPreKeySignature').length).toBe(1);
  662. expect(iq_el.querySelectorAll('identityKey').length).toBe(1);
  663. stanza = $iq({
  664. 'from': _converse.bare_jid,
  665. 'id': iq_el.getAttribute('id'),
  666. 'to': _converse.bare_jid,
  667. 'type': 'result'});
  668. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  669. await _converse.api.waitUntil('OMEMOInitialized', 1000);
  670. await test_utils.openChatBoxFor(_converse, contact_jid);
  671. iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
  672. expect(iq_stanza.toLocaleString()).toBe(
  673. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
  674. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  675. `<items node="eu.siacs.conversations.axolotl.devicelist"/>`+
  676. `</pubsub>`+
  677. `</iq>`);
  678. stanza = $iq({
  679. 'from': contact_jid,
  680. 'id': iq_stanza.nodeTree.getAttribute('id'),
  681. 'to': _converse.bare_jid,
  682. 'type': 'result',
  683. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  684. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  685. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  686. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  687. .c('device', {'id': '368866411b877c30064a5f62b917cffe'}).up()
  688. .c('device', {'id': '3300659945416e274474e469a1f0154c'}).up()
  689. .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
  690. .c('device', {'id': 'ae890ac52d0df67ed7cfdf51b644e901'});
  691. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  692. devicelist = _converse.devicelists.get(contact_jid);
  693. await test_utils.waitUntil(() => devicelist.devices.length);
  694. expect(_converse.devicelists.length).toBe(2);
  695. devicelist = _converse.devicelists.get(contact_jid);
  696. expect(devicelist.devices.length).toBe(4);
  697. expect(devicelist.devices.at(0).get('id')).toBe('368866411b877c30064a5f62b917cffe');
  698. expect(devicelist.devices.at(1).get('id')).toBe('3300659945416e274474e469a1f0154c');
  699. expect(devicelist.devices.at(2).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
  700. expect(devicelist.devices.at(3).get('id')).toBe('ae890ac52d0df67ed7cfdf51b644e901');
  701. await test_utils.waitUntil(() => _converse.chatboxviews.get(contact_jid).el.querySelector('.chat-toolbar'));
  702. const view = _converse.chatboxviews.get(contact_jid);
  703. const toolbar = view.el.querySelector('.chat-toolbar');
  704. expect(view.model.get('omemo_active')).toBe(undefined);
  705. let toggle = toolbar.querySelector('.toggle-omemo');
  706. expect(_.isNull(toggle)).toBe(false);
  707. expect(u.hasClass('fa-unlock', toggle)).toBe(true);
  708. expect(u.hasClass('fa-lock', toggle)).toBe(false);
  709. spyOn(view, 'toggleOMEMO').and.callThrough();
  710. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  711. toolbar.querySelector('.toggle-omemo').click();
  712. expect(view.toggleOMEMO).toHaveBeenCalled();
  713. expect(view.model.get('omemo_active')).toBe(true);
  714. await test_utils.waitUntil(() => u.hasClass('fa-lock', toolbar.querySelector('.toggle-omemo')));
  715. toggle = toolbar.querySelector('.toggle-omemo');
  716. expect(u.hasClass('fa-unlock', toggle)).toBe(false);
  717. expect(u.hasClass('fa-lock', toggle)).toBe(true);
  718. const textarea = view.el.querySelector('.chat-textarea');
  719. textarea.value = 'This message will be sent encrypted';
  720. view.keyPressed({
  721. target: textarea,
  722. preventDefault: _.noop,
  723. keyCode: 13
  724. });
  725. done();
  726. }));
  727. it("shows OMEMO device fingerprints in the user details modal",
  728. mock.initConverseWithPromises(
  729. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  730. async function (done, _converse) {
  731. test_utils.createContacts(_converse, 'current', 1);
  732. _converse.emit('rosterContactsFetched');
  733. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  734. await test_utils.openChatBoxFor(_converse, contact_jid)
  735. // We simply emit, to avoid doing all the setup work
  736. _converse.emit('OMEMOInitialized');
  737. const view = _converse.chatboxviews.get(contact_jid);
  738. const show_modal_button = view.el.querySelector('.show-user-details-modal');
  739. show_modal_button.click();
  740. const modal = view.user_details_modal;
  741. await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
  742. let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
  743. expect(iq_stanza.toLocaleString()).toBe(
  744. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="max.frankfurter@localhost" type="get" xmlns="jabber:client">`+
  745. `<pubsub xmlns="http://jabber.org/protocol/pubsub"><items node="eu.siacs.conversations.axolotl.devicelist"/></pubsub>`+
  746. `</iq>`);
  747. let stanza = $iq({
  748. 'from': contact_jid,
  749. 'id': iq_stanza.nodeTree.getAttribute('id'),
  750. 'to': _converse.bare_jid,
  751. 'type': 'result',
  752. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  753. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  754. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  755. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  756. .c('device', {'id': '555'});
  757. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  758. await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
  759. iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '555'));
  760. expect(iq_stanza.toLocaleString()).toBe(
  761. `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="max.frankfurter@localhost" type="get" xmlns="jabber:client">`+
  762. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  763. `<items node="eu.siacs.conversations.axolotl.bundles:555"/>`+
  764. `</pubsub>`+
  765. `</iq>`);
  766. stanza = $iq({
  767. 'from': contact_jid,
  768. 'id': iq_stanza.nodeTree.getAttribute('id'),
  769. 'to': _converse.bare_jid,
  770. 'type': 'result',
  771. }).c('pubsub', {
  772. 'xmlns': 'http://jabber.org/protocol/pubsub'
  773. }).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:555"})
  774. .c('item')
  775. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  776. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('1111')).up()
  777. .c('signedPreKeySignature').t(btoa('2222')).up()
  778. .c('identityKey').t('BQmHEOHjsYm3w5M8VqxAtqJmLCi7CaxxsdZz6G0YpuMI').up()
  779. .c('prekeys')
  780. .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1001')).up()
  781. .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up()
  782. .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
  783. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  784. await test_utils.waitUntil(() => modal.el.querySelectorAll('.fingerprints .fingerprint').length);
  785. expect(modal.el.querySelectorAll('.fingerprints .fingerprint').length).toBe(1);
  786. const el = modal.el.querySelector('.fingerprints .fingerprint');
  787. expect(el.textContent.trim()).toBe(
  788. u.formatFingerprint(u.arrayBufferToHex(u.base64ToArrayBuffer('BQmHEOHjsYm3w5M8VqxAtqJmLCi7CaxxsdZz6G0YpuMI')))
  789. );
  790. expect(modal.el.querySelectorAll('input[type="radio"]').length).toBe(2);
  791. const devicelist = _converse.devicelists.get(contact_jid);
  792. expect(devicelist.devices.get('555').get('trusted')).toBe(0);
  793. let trusted_radio = modal.el.querySelector('input[type="radio"][name="555"][value="1"]');
  794. expect(trusted_radio.checked).toBe(true);
  795. let untrusted_radio = modal.el.querySelector('input[type="radio"][name="555"][value="-1"]');
  796. expect(untrusted_radio.checked).toBe(false);
  797. // Test that the device can be set to untrusted
  798. untrusted_radio.click();
  799. trusted_radio = document.querySelector('input[type="radio"][name="555"][value="1"]');
  800. expect(trusted_radio.hasAttribute('checked')).toBe(false);
  801. expect(devicelist.devices.get('555').get('trusted')).toBe(-1);
  802. untrusted_radio = document.querySelector('input[type="radio"][name="555"][value="-1"]');
  803. expect(untrusted_radio.hasAttribute('checked')).toBe(true);
  804. trusted_radio.click();
  805. expect(devicelist.devices.get('555').get('trusted')).toBe(1);
  806. done();
  807. }));
  808. });
  809. describe("A chatbox with an active OMEMO session", function() {
  810. it("will not show the spoiler toolbar button",
  811. mock.initConverseWithPromises(
  812. null, ['rosterGroupsFetched'], {},
  813. function (done, _converse) {
  814. // TODO
  815. done()
  816. }));
  817. });
  818. }));