UNPKG

14.4 kBJavaScriptView Raw
1import 'source-map-support/register';
2
3import '../olm-loader';
4
5import Crypto from '../../lib/crypto';
6import expect from 'expect';
7
8import WebStorageSessionStore from '../../lib/store/session/webstorage';
9import MemoryCryptoStore from '../../lib/crypto/store/memory-crypto-store.js';
10import MockStorageApi from '../MockStorageApi';
11import TestClient from '../TestClient';
12import {MatrixEvent} from '../../lib/models/event';
13import Room from '../../lib/models/room';
14import olmlib from '../../lib/crypto/olmlib';
15import lolex from 'lolex';
16
17const EventEmitter = require("events").EventEmitter;
18
19const sdk = require("../..");
20
21const Olm = global.Olm;
22
23describe("Crypto", function() {
24 if (!sdk.CRYPTO_ENABLED) {
25 return;
26 }
27
28 beforeEach(function(done) {
29 Olm.init().then(done);
30 });
31
32 it("Crypto exposes the correct olm library version", function() {
33 expect(Crypto.getOlmVersion()[0]).toEqual(3);
34 });
35
36
37 describe('Session management', function() {
38 const otkResponse = {
39 one_time_keys: {
40 '@alice:home.server': {
41 aliceDevice: {
42 'signed_curve25519:FLIBBLE': {
43 key: 'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI',
44 signatures: {
45 '@alice:home.server': {
46 'ed25519:aliceDevice': 'totally a valid signature',
47 },
48 },
49 },
50 },
51 },
52 },
53 };
54 let crypto;
55 let mockBaseApis;
56 let mockRoomList;
57
58 let fakeEmitter;
59
60 beforeEach(async function() {
61 const mockStorage = new MockStorageApi();
62 const sessionStore = new WebStorageSessionStore(mockStorage);
63 const cryptoStore = new MemoryCryptoStore(mockStorage);
64
65 cryptoStore.storeEndToEndDeviceData({
66 devices: {
67 '@bob:home.server': {
68 'BOBDEVICE': {
69 keys: {
70 'curve25519:BOBDEVICE': 'this is a key',
71 },
72 },
73 },
74 },
75 trackingStatus: {},
76 });
77
78 mockBaseApis = {
79 sendToDevice: expect.createSpy(),
80 getKeyBackupVersion: expect.createSpy(),
81 isGuest: expect.createSpy(),
82 };
83 mockRoomList = {};
84
85 fakeEmitter = new EventEmitter();
86
87 crypto = new Crypto(
88 mockBaseApis,
89 sessionStore,
90 "@alice:home.server",
91 "FLIBBLE",
92 sessionStore,
93 cryptoStore,
94 mockRoomList,
95 );
96 crypto.registerEventHandlers(fakeEmitter);
97 await crypto.init();
98 });
99
100 afterEach(async function() {
101 await crypto.stop();
102 });
103
104 it("restarts wedged Olm sessions", async function() {
105 const prom = new Promise((resolve) => {
106 mockBaseApis.claimOneTimeKeys = function() {
107 resolve();
108 return otkResponse;
109 };
110 });
111
112 fakeEmitter.emit('toDeviceEvent', {
113 getType: expect.createSpy().andReturn('m.room.message'),
114 getContent: expect.createSpy().andReturn({
115 msgtype: 'm.bad.encrypted',
116 }),
117 getWireContent: expect.createSpy().andReturn({
118 algorithm: 'm.olm.v1.curve25519-aes-sha2',
119 sender_key: 'this is a key',
120 }),
121 getSender: expect.createSpy().andReturn('@bob:home.server'),
122 });
123
124 await prom;
125 });
126 });
127
128 describe('Key requests', function() {
129 let aliceClient;
130 let bobClient;
131
132 beforeEach(async function() {
133 aliceClient = (new TestClient(
134 "@alice:example.com", "alicedevice",
135 )).client;
136 bobClient = (new TestClient(
137 "@bob:example.com", "bobdevice",
138 )).client;
139 await aliceClient.initCrypto();
140 await bobClient.initCrypto();
141 });
142
143 afterEach(async function() {
144 aliceClient.stopClient();
145 bobClient.stopClient();
146 });
147
148 it(
149 "does not cancel keyshare requests if some messages are not decrypted",
150 async function() {
151 function awaitEvent(emitter, event) {
152 return new Promise((resolve, reject) => {
153 emitter.once(event, (result) => {
154 resolve(result);
155 });
156 });
157 }
158
159 async function keyshareEventForEvent(event, index) {
160 const eventContent = event.getWireContent();
161 const key = await aliceClient._crypto._olmDevice
162 .getInboundGroupSessionKey(
163 roomId, eventContent.sender_key, eventContent.session_id,
164 index,
165 );
166 const ksEvent = new MatrixEvent({
167 type: "m.forwarded_room_key",
168 sender: "@alice:example.com",
169 content: {
170 algorithm: olmlib.MEGOLM_ALGORITHM,
171 room_id: roomId,
172 sender_key: eventContent.sender_key,
173 sender_claimed_ed25519_key: key.sender_claimed_ed25519_key,
174 session_id: eventContent.session_id,
175 session_key: key.key,
176 chain_index: key.chain_index,
177 forwarding_curve25519_key_chain:
178 key.forwarding_curve_key_chain,
179 },
180 });
181 // make onRoomKeyEvent think this was an encrypted event
182 ksEvent._senderCurve25519Key = "akey";
183 return ksEvent;
184 }
185
186 const encryptionCfg = {
187 "algorithm": "m.megolm.v1.aes-sha2",
188 };
189 const roomId = "!someroom";
190 const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
191 const bobRoom = new Room(roomId, bobClient, "@bob:example.com", {});
192 aliceClient.store.storeRoom(aliceRoom);
193 bobClient.store.storeRoom(bobRoom);
194 await aliceClient.setRoomEncryption(roomId, encryptionCfg);
195 await bobClient.setRoomEncryption(roomId, encryptionCfg);
196 const events = [
197 new MatrixEvent({
198 type: "m.room.message",
199 sender: "@alice:example.com",
200 room_id: roomId,
201 event_id: "$1",
202 content: {
203 msgtype: "m.text",
204 body: "1",
205 },
206 }),
207 new MatrixEvent({
208 type: "m.room.message",
209 sender: "@alice:example.com",
210 room_id: roomId,
211 event_id: "$2",
212 content: {
213 msgtype: "m.text",
214 body: "2",
215 },
216 }),
217 ];
218 await Promise.all(events.map(async (event) => {
219 // alice encrypts each event, and then bob tries to decrypt
220 // them without any keys, so that they'll be in pending
221 await aliceClient._crypto.encryptEvent(event, aliceRoom);
222 event._clearEvent = {};
223 event._senderCurve25519Key = null;
224 event._claimedEd25519Key = null;
225 try {
226 await bobClient._crypto.decryptEvent(event);
227 } catch (e) {
228 // we expect this to fail because we don't have the
229 // decryption keys yet
230 }
231 }));
232
233 const bobDecryptor = bobClient._crypto._getRoomDecryptor(
234 roomId, olmlib.MEGOLM_ALGORITHM,
235 );
236
237 let eventPromise = Promise.all(events.map((ev) => {
238 return awaitEvent(ev, "Event.decrypted");
239 }));
240
241 // keyshare the session key starting at the second message, so
242 // the first message can't be decrypted yet, but the second one
243 // can
244 let ksEvent = await keyshareEventForEvent(events[1], 1);
245 await bobDecryptor.onRoomKeyEvent(ksEvent);
246 await eventPromise;
247 expect(events[0].getContent().msgtype).toBe("m.bad.encrypted");
248 expect(events[1].getContent().msgtype).toNotBe("m.bad.encrypted");
249
250 const cryptoStore = bobClient._cryptoStore;
251 const eventContent = events[0].getWireContent();
252 const senderKey = eventContent.sender_key;
253 const sessionId = eventContent.session_id;
254 const roomKeyRequestBody = {
255 algorithm: olmlib.MEGOLM_ALGORITHM,
256 room_id: roomId,
257 sender_key: senderKey,
258 session_id: sessionId,
259 };
260 // the room key request should still be there, since we haven't
261 // decrypted everything
262 expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
263 .toExist();
264
265 // keyshare the session key starting at the first message, so
266 // that it can now be decrypted
267 eventPromise = awaitEvent(events[0], "Event.decrypted");
268 ksEvent = await keyshareEventForEvent(events[0], 0);
269 await bobDecryptor.onRoomKeyEvent(ksEvent);
270 await eventPromise;
271 expect(events[0].getContent().msgtype).toNotBe("m.bad.encrypted");
272 // the room key request should be gone since we've now decypted everything
273 expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
274 .toNotExist();
275 },
276 );
277
278 it("creates a new keyshare request if we request a keyshare", async function() {
279 // make sure that cancelAndResend... creates a new keyshare request
280 // if there wasn't an already-existing one
281 const event = new MatrixEvent({
282 sender: "@bob:example.com",
283 room_id: "!someroom",
284 content: {
285 algorithm: olmlib.MEGOLM_ALGORITHM,
286 session_id: "sessionid",
287 sender_key: "senderkey",
288 },
289 });
290 await aliceClient.cancelAndResendEventRoomKeyRequest(event);
291 const cryptoStore = aliceClient._cryptoStore;
292 const roomKeyRequestBody = {
293 algorithm: olmlib.MEGOLM_ALGORITHM,
294 room_id: "!someroom",
295 session_id: "sessionid",
296 sender_key: "senderkey",
297 };
298 expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
299 .toExist();
300 });
301
302 it("uses a new txnid for re-requesting keys", async function() {
303 const event = new MatrixEvent({
304 sender: "@bob:example.com",
305 room_id: "!someroom",
306 content: {
307 algorithm: olmlib.MEGOLM_ALGORITHM,
308 session_id: "sessionid",
309 sender_key: "senderkey",
310 },
311 });
312 /* return a promise and a function. When the function is called,
313 * the promise will be resolved.
314 */
315 function awaitFunctionCall() {
316 let func;
317 const promise = new Promise((resolve, reject) => {
318 func = function(...args) {
319 resolve(args);
320 return new Promise((resolve, reject) => {
321 // give us some time to process the result before
322 // continuing
323 global.setTimeout(resolve, 1);
324 });
325 };
326 });
327 return {func, promise};
328 }
329
330 aliceClient.startClient();
331
332 const clock = lolex.install();
333
334 try {
335 let promise;
336 // make a room key request, and record the transaction ID for the
337 // sendToDevice call
338 ({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
339 await aliceClient.cancelAndResendEventRoomKeyRequest(event);
340 clock.runToLast();
341 let args = await promise;
342 const txnId = args[2];
343 clock.runToLast();
344
345 // give the room key request manager time to update the state
346 // of the request
347 await Promise.resolve();
348
349 // cancel and resend the room key request
350 ({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
351 await aliceClient.cancelAndResendEventRoomKeyRequest(event);
352 clock.runToLast();
353 // the first call to sendToDevice will be the cancellation
354 args = await promise;
355 // the second call to sendToDevice will be the key request
356 ({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
357 clock.runToLast();
358 args = await promise;
359 clock.runToLast();
360 expect(args[2]).toNotBe(txnId);
361 } finally {
362 clock.uninstall();
363 }
364 });
365 });
366});