omemo.js 61 KB

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