1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-08 05:01:59 +03:00

Rewrite megolm integration tests with async arrow functions (#2519)

This commit is contained in:
Faye Duxovni
2022-07-21 06:41:46 -04:00
committed by GitHub
parent 32f55de383
commit 45db39ec88
2 changed files with 693 additions and 771 deletions

View File

@ -24,13 +24,13 @@ import {
IContent, IContent,
IEvent, IEvent,
IClaimOTKsResult, IClaimOTKsResult,
IJoinedRoom,
ISyncResponse, ISyncResponse,
IDownloadKeyResult, IDownloadKeyResult,
MatrixEvent, MatrixEvent,
MatrixEventEvent, MatrixEventEvent,
} from "../../src/matrix"; } from "../../src/matrix";
import { IDeviceKeys } from "../../src/crypto/dehydration"; import { IDeviceKeys } from "../../src/crypto/dehydration";
import { ISignatures } from "../../src/@types/signed";
const ROOM_ID = "!room:id"; const ROOM_ID = "!room:id";
@ -75,18 +75,17 @@ function encryptOlmEvent(opts: {
type: opts.plaintype || 'm.test', type: opts.plaintype || 'm.test',
}; };
const event = { return {
content: { content: {
algorithm: 'm.olm.v1.curve25519-aes-sha2', algorithm: 'm.olm.v1.curve25519-aes-sha2',
ciphertext: {}, ciphertext: {
[opts.recipient.getDeviceKey()]: opts.p2pSession.encrypt(JSON.stringify(plaintext)),
},
sender_key: opts.senderKey, sender_key: opts.senderKey,
}, },
sender: opts.sender || '@bob:xyz', sender: opts.sender || '@bob:xyz',
type: 'm.room.encrypted', type: 'm.room.encrypted',
}; };
event.content.ciphertext[opts.recipient.getDeviceKey()] =
opts.p2pSession.encrypt(JSON.stringify(plaintext));
return event;
} }
// encrypt an event with megolm // encrypt an event with megolm
@ -151,43 +150,54 @@ function encryptGroupSessionKey(opts: {
// get a /sync response which contains a single room (ROOM_ID), with the members given // get a /sync response which contains a single room (ROOM_ID), with the members given
function getSyncResponse(roomMembers: string[]): ISyncResponse { function getSyncResponse(roomMembers: string[]): ISyncResponse {
const roomResponse = { const roomResponse: IJoinedRoom = {
summary: {
"m.heroes": [],
"m.joined_member_count": roomMembers.length,
"m.invited_member_count": roomMembers.length,
},
state: { state: {
events: [ events: [
testUtils.mkEvent({ testUtils.mkEventCustom({
sender: roomMembers[0],
type: 'm.room.encryption', type: 'm.room.encryption',
skey: '', state_key: '',
content: { content: {
algorithm: 'm.megolm.v1.aes-sha2', algorithm: 'm.megolm.v1.aes-sha2',
}, },
}), }),
], ],
}, },
timeline: {
events: [],
prev_batch: '',
},
ephemeral: { events: [] },
account_data: { events: [] },
unread_notifications: {},
}; };
for (let i = 0; i < roomMembers.length; i++) { for (let i = 0; i < roomMembers.length; i++) {
roomResponse.state.events.push( roomResponse.state.events.push(
testUtils.mkMembership({ testUtils.mkMembershipCustom({
mship: 'join', membership: 'join',
sender: roomMembers[i], sender: roomMembers[i],
}), }),
); );
} }
const syncResponse = { return {
next_batch: "1", next_batch: "1",
rooms: { rooms: {
join: {}, join: { [ROOM_ID]: roomResponse },
invite: {}, invite: {},
leave: {}, leave: {},
}, },
account_data: { events: [] }, account_data: { events: [] },
}; };
syncResponse.rooms.join[ROOM_ID] = roomResponse;
return syncResponse;
} }
describe("megolm", function() { describe("megolm", () => {
if (!global.Olm) { if (!global.Olm) {
logger.warn('not running megolm tests: Olm not present'); logger.warn('not running megolm tests: Olm not present');
return; return;
@ -218,21 +228,12 @@ describe("megolm", function() {
}; };
const j = anotherjson.stringify(testDeviceKeys); const j = anotherjson.stringify(testDeviceKeys);
const sig = testOlmAccount.sign(j); const sig = testOlmAccount.sign(j);
testDeviceKeys.signatures = {}; testDeviceKeys.signatures = { [userId]: { 'ed25519:DEVICE_ID': sig } };
testDeviceKeys.signatures[userId] = {
'ed25519:DEVICE_ID': sig,
};
const queryResponse = { return {
device_keys: {}, device_keys: { [userId]: { 'DEVICE_ID': testDeviceKeys } },
failures: {}, failures: {},
}; };
queryResponse.device_keys[userId] = {
'DEVICE_ID': testDeviceKeys,
};
return queryResponse;
} }
/** /**
@ -249,26 +250,21 @@ describe("megolm", function() {
const keyId = Object.keys(testOneTimeKeys.curve25519)[0]; const keyId = Object.keys(testOneTimeKeys.curve25519)[0];
const oneTimeKey: string = testOneTimeKeys.curve25519[keyId]; const oneTimeKey: string = testOneTimeKeys.curve25519[keyId];
const keyResult: { key: string, signatures: ISignatures } = { const unsignedKeyResult = { key: oneTimeKey };
key: oneTimeKey, const j = anotherjson.stringify(unsignedKeyResult);
signatures: {},
};
const j = anotherjson.stringify({ key: oneTimeKey });
const sig = testOlmAccount.sign(j); const sig = testOlmAccount.sign(j);
keyResult.signatures[userId] = { const keyResult = {
'ed25519:DEVICE_ID': sig, ...unsignedKeyResult,
signatures: { [userId]: { 'ed25519:DEVICE_ID': sig } },
}; };
const claimResponse = { one_time_keys: {}, failures: {} }; return {
claimResponse.one_time_keys[userId] = { one_time_keys: { [userId]: { 'DEVICE_ID': { ['signed_curve25519:' + keyId]: keyResult } } },
'DEVICE_ID': {}, failures: {},
}; };
claimResponse.one_time_keys[userId].DEVICE_ID['signed_curve25519:' + keyId] =
keyResult;
return claimResponse;
} }
beforeEach(async function() { beforeEach(async () => {
aliceTestClient = new TestClient( aliceTestClient = new TestClient(
"@alice:localhost", "xzcvb", "akjgkrgjs", "@alice:localhost", "xzcvb", "akjgkrgjs",
); );
@ -280,14 +276,11 @@ describe("megolm", function() {
testSenderKey = testE2eKeys.curve25519; testSenderKey = testE2eKeys.curve25519;
}); });
afterEach(function() { afterEach(() => aliceTestClient.stop());
return aliceTestClient.stop();
});
it("Alice receives a megolm message", function() { it("Alice receives a megolm message", async () => {
return aliceTestClient.start().then(() => { await aliceTestClient.start();
return createOlmSession(testOlmAccount, aliceTestClient); const p2pSession = await createOlmSession(testOlmAccount, aliceTestClient);
}).then((p2pSession) => {
const groupSession = new Olm.OutboundGroupSession(); const groupSession = new Olm.OutboundGroupSession();
groupSession.create(); groupSession.create();
@ -314,39 +307,31 @@ describe("megolm", function() {
events: [roomKeyEncrypted], events: [roomKeyEncrypted],
}, },
rooms: { rooms: {
join: {}, join: {
[ROOM_ID]: { timeline: { events: [messageEncrypted] } },
}, },
};
syncResponse.rooms.join[ROOM_ID] = {
timeline: {
events: [messageEncrypted],
}, },
}; };
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse); aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse);
return aliceTestClient.flushSync(); await aliceTestClient.flushSync();
}).then(function() {
const room = aliceTestClient.client.getRoom(ROOM_ID); const room = aliceTestClient.client.getRoom(ROOM_ID);
const event = room.getLiveTimeline().getEvents()[0]; const event = room.getLiveTimeline().getEvents()[0];
expect(event.isEncrypted()).toBe(true); expect(event.isEncrypted()).toBe(true);
return testUtils.awaitDecryption(event); const decryptedEvent = await testUtils.awaitDecryption(event);
}).then((event) => { expect(decryptedEvent.getContent().body).toEqual('42');
expect(event.getContent().body).toEqual('42');
});
}); });
it("Alice receives a megolm message before the session keys", function() { it("Alice receives a megolm message before the session keys", async () => {
// https://github.com/vector-im/element-web/issues/2273 // https://github.com/vector-im/element-web/issues/2273
let roomKeyEncrypted; await aliceTestClient.start();
const p2pSession = await createOlmSession(testOlmAccount, aliceTestClient);
return aliceTestClient.start().then(() => {
return createOlmSession(testOlmAccount, aliceTestClient);
}).then((p2pSession) => {
const groupSession = new Olm.OutboundGroupSession(); const groupSession = new Olm.OutboundGroupSession();
groupSession.create(); groupSession.create();
// make the room_key event, but don't send it yet // make the room_key event, but don't send it yet
roomKeyEncrypted = encryptGroupSessionKey({ const roomKeyEncrypted = encryptGroupSessionKey({
senderKey: testSenderKey, senderKey: testSenderKey,
recipient: aliceTestClient, recipient: aliceTestClient,
p2pSession: p2pSession, p2pSession: p2pSession,
@ -362,58 +347,43 @@ describe("megolm", function() {
}); });
// Alice just gets the message event to start with // Alice just gets the message event to start with
const syncResponse = { aliceTestClient.httpBackend.when("GET", "/sync").respond(200, {
next_batch: 1, next_batch: 1,
rooms: { rooms: { join: { [ROOM_ID]: { timeline: { events: [messageEncrypted] } } } },
join: {}, });
}, await aliceTestClient.flushSync();
};
syncResponse.rooms.join[ROOM_ID] = {
timeline: {
events: [messageEncrypted],
},
};
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse);
return aliceTestClient.flushSync();
}).then(function() {
const room = aliceTestClient.client.getRoom(ROOM_ID); const room = aliceTestClient.client.getRoom(ROOM_ID);
const event = room.getLiveTimeline().getEvents()[0]; expect(room.getLiveTimeline().getEvents()[0].getContent().msgtype).toEqual('m.bad.encrypted');
expect(event.getContent().msgtype).toEqual('m.bad.encrypted');
// now she gets the room_key event // now she gets the room_key event
const syncResponse = { aliceTestClient.httpBackend.when("GET", "/sync").respond(200, {
next_batch: 2, next_batch: 2,
to_device: { to_device: {
events: [roomKeyEncrypted], events: [roomKeyEncrypted],
}, },
}; });
await aliceTestClient.flushSync();
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse);
return aliceTestClient.flushSync();
}).then(function() {
const room = aliceTestClient.client.getRoom(ROOM_ID);
const event = room.getLiveTimeline().getEvents()[0]; const event = room.getLiveTimeline().getEvents()[0];
let decryptedEvent: MatrixEvent;
if (event.getContent().msgtype != 'm.bad.encrypted') { if (event.getContent().msgtype != 'm.bad.encrypted') {
return event; decryptedEvent = event;
} } else {
decryptedEvent = await new Promise<MatrixEvent>((resolve) => {
return new Promise<MatrixEvent>((resolve, reject) => {
event.once(MatrixEventEvent.Decrypted, (ev) => { event.once(MatrixEventEvent.Decrypted, (ev) => {
logger.log(`${Date.now()} event ${event.getId()} now decrypted`); logger.log(`${Date.now()} event ${event.getId()} now decrypted`);
resolve(ev); resolve(ev);
}); });
}); });
}).then((event) => { }
expect(event.getContent().body).toEqual('42'); expect(decryptedEvent.getContent().body).toEqual('42');
});
}); });
it("Alice gets a second room_key message", function() { it("Alice gets a second room_key message", async () => {
return aliceTestClient.start().then(() => { await aliceTestClient.start();
return createOlmSession(testOlmAccount, aliceTestClient); const p2pSession = await createOlmSession(testOlmAccount, aliceTestClient);
}).then((p2pSession) => {
const groupSession = new Olm.OutboundGroupSession(); const groupSession = new Olm.OutboundGroupSession();
groupSession.create(); groupSession.create();
@ -460,37 +430,26 @@ describe("megolm", function() {
events: [roomKeyEncrypted2], events: [roomKeyEncrypted2],
}, },
rooms: { rooms: {
join: {}, join: { [ROOM_ID]: { timeline: { events: [messageEncrypted] } } },
},
};
syncResponse2.rooms.join[ROOM_ID] = {
timeline: {
events: [messageEncrypted],
}, },
}; };
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse2); aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse2);
// flush both syncs // flush both syncs
return aliceTestClient.flushSync().then(() => { await aliceTestClient.flushSync();
return aliceTestClient.flushSync(); await aliceTestClient.flushSync();
});
}).then(async function() {
const room = aliceTestClient.client.getRoom(ROOM_ID); const room = aliceTestClient.client.getRoom(ROOM_ID);
await room.decryptCriticalEvents(); await room.decryptCriticalEvents();
const event = room.getLiveTimeline().getEvents()[0]; const event = room.getLiveTimeline().getEvents()[0];
expect(event.getContent().body).toEqual('42'); expect(event.getContent().body).toEqual('42');
}); });
});
it('Alice sends a megolm message', function() {
let p2pSession;
it('Alice sends a megolm message', async () => {
aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} }, failures: {} }); aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} }, failures: {} });
return aliceTestClient.start().then(() => { await aliceTestClient.start();
// establish an olm session with alice // establish an olm session with alice
return createOlmSession(testOlmAccount, aliceTestClient); const p2pSession = await createOlmSession(testOlmAccount, aliceTestClient);
}).then((_p2pSession) => {
p2pSession = _p2pSession;
const syncResponse = getSyncResponse(['@bob:xyz']); const syncResponse = getSyncResponse(['@bob:xyz']);
@ -503,14 +462,14 @@ describe("megolm", function() {
syncResponse.to_device = { events: [olmEvent] }; syncResponse.to_device = { events: [olmEvent] };
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse); aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
return aliceTestClient.flushSync(); await aliceTestClient.flushSync();
}).then(function() {
// start out with the device unknown - the send should be rejected. // start out with the device unknown - the send should be rejected.
aliceTestClient.httpBackend.when('POST', '/keys/query').respond( aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
200, getTestKeysQueryResponse('@bob:xyz'), 200, getTestKeysQueryResponse('@bob:xyz'),
); );
return Promise.all([ await Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test').then(() => { aliceTestClient.client.sendTextMessage(ROOM_ID, 'test').then(() => {
throw new Error("sendTextMessage failed on an unknown device"); throw new Error("sendTextMessage failed on an unknown device");
}, (e) => { }, (e) => {
@ -518,14 +477,14 @@ describe("megolm", function() {
}), }),
aliceTestClient.httpBackend.flushAllExpected(), aliceTestClient.httpBackend.flushAllExpected(),
]); ]);
}).then(function() {
// mark the device as known, and resend. // mark the device as known, and resend.
aliceTestClient.client.setDeviceKnown('@bob:xyz', 'DEVICE_ID'); aliceTestClient.client.setDeviceKnown('@bob:xyz', 'DEVICE_ID');
let inboundGroupSession; let inboundGroupSession: Olm.InboundGroupSession;
aliceTestClient.httpBackend.when( aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/m.room.encrypted/', 'PUT', '/sendToDevice/m.room.encrypted/',
).respond(200, function(path, content) { ).respond(200, function(_path, content) {
const m = content.messages['@bob:xyz'].DEVICE_ID; const m = content.messages['@bob:xyz'].DEVICE_ID;
const ct = m.ciphertext[testSenderKey]; const ct = m.ciphertext[testSenderKey];
const decrypted = JSON.parse(p2pSession.decrypt(ct.type, ct.body)); const decrypted = JSON.parse(p2pSession.decrypt(ct.type, ct.body));
@ -538,9 +497,9 @@ describe("megolm", function() {
aliceTestClient.httpBackend.when( aliceTestClient.httpBackend.when(
'PUT', '/send/', 'PUT', '/send/',
).respond(200, function(path, content) { ).respond(200, (_path, content: IContent) => {
const ct = content.ciphertext; const ct = content.ciphertext;
const r = inboundGroupSession.decrypt(ct); const r: any = inboundGroupSession.decrypt(ct);
logger.log('Decrypted received megolm message', r); logger.log('Decrypted received megolm message', r);
expect(r.message_index).toEqual(0); expect(r.message_index).toEqual(0);
@ -548,31 +507,25 @@ describe("megolm", function() {
expect(decrypted.type).toEqual('m.room.message'); expect(decrypted.type).toEqual('m.room.message');
expect(decrypted.content.body).toEqual('test'); expect(decrypted.content.body).toEqual('test');
return { return { event_id: '$event_id' };
event_id: '$event_id',
};
}); });
const room = aliceTestClient.client.getRoom(ROOM_ID); const room = aliceTestClient.client.getRoom(ROOM_ID);
const pendingMsg = room.getPendingEvents()[0]; const pendingMsg = room.getPendingEvents()[0];
return Promise.all([ await Promise.all([
aliceTestClient.client.resendEvent(pendingMsg, room), aliceTestClient.client.resendEvent(pendingMsg, room),
// the crypto stuff can take a while, so give the requests a whole second. // the crypto stuff can take a while, so give the requests a whole second.
aliceTestClient.httpBackend.flushAllExpected({ aliceTestClient.httpBackend.flushAllExpected({ timeout: 1000 }),
timeout: 1000,
}),
]); ]);
}); });
});
it("We shouldn't attempt to send to blocked devices", function() { it("We shouldn't attempt to send to blocked devices", async () => {
aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} }, failures: {} }); aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} }, failures: {} });
return aliceTestClient.start().then(() => { await aliceTestClient.start();
// establish an olm session with alice // establish an olm session with alice
return createOlmSession(testOlmAccount, aliceTestClient); const p2pSession = await createOlmSession(testOlmAccount, aliceTestClient);
}).then((p2pSession) => {
const syncResponse = getSyncResponse(['@bob:xyz']); const syncResponse = getSyncResponse(['@bob:xyz']);
const olmEvent = encryptOlmEvent({ const olmEvent = encryptOlmEvent({
@ -584,19 +537,19 @@ describe("megolm", function() {
syncResponse.to_device = { events: [olmEvent] }; syncResponse.to_device = { events: [olmEvent] };
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse); aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
return aliceTestClient.flushSync(); await aliceTestClient.flushSync();
}).then(function() {
logger.log('Forcing alice to download our device keys'); logger.log('Forcing alice to download our device keys');
aliceTestClient.httpBackend.when('POST', '/keys/query').respond( aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
200, getTestKeysQueryResponse('@bob:xyz'), 200, getTestKeysQueryResponse('@bob:xyz'),
); );
return Promise.all([ await Promise.all([
aliceTestClient.client.downloadKeys(['@bob:xyz']), aliceTestClient.client.downloadKeys(['@bob:xyz']),
aliceTestClient.httpBackend.flush('/keys/query', 1), aliceTestClient.httpBackend.flush('/keys/query', 1),
]); ]);
}).then(function() {
logger.log('Telling alice to block our device'); logger.log('Telling alice to block our device');
aliceTestClient.client.setDeviceBlocked('@bob:xyz', 'DEVICE_ID'); aliceTestClient.client.setDeviceBlocked('@bob:xyz', 'DEVICE_ID');
@ -610,27 +563,19 @@ describe("megolm", function() {
'PUT', '/sendToDevice/m.room_key.withheld/', 'PUT', '/sendToDevice/m.room_key.withheld/',
).respond(200, {}); ).respond(200, {});
return Promise.all([ await Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'), aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'),
// the crypto stuff can take a while, so give the requests a whole second. // the crypto stuff can take a while, so give the requests a whole second.
aliceTestClient.httpBackend.flushAllExpected({ aliceTestClient.httpBackend.flushAllExpected({ timeout: 1000 }),
timeout: 1000,
}),
]); ]);
}); });
});
it("We should start a new megolm session when a device is blocked", function() {
let p2pSession;
let megolmSessionId;
it("We should start a new megolm session when a device is blocked", async () => {
aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} }, failures: {} }); aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} }, failures: {} });
return aliceTestClient.start().then(() => { await aliceTestClient.start();
// establish an olm session with alice // establish an olm session with alice
return createOlmSession(testOlmAccount, aliceTestClient); const p2pSession = await createOlmSession(testOlmAccount, aliceTestClient);
}).then((_p2pSession) => {
p2pSession = _p2pSession;
const syncResponse = getSyncResponse(['@bob:xyz']); const syncResponse = getSyncResponse(['@bob:xyz']);
@ -643,26 +588,26 @@ describe("megolm", function() {
syncResponse.to_device = { events: [olmEvent] }; syncResponse.to_device = { events: [olmEvent] };
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse); aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
return aliceTestClient.flushSync(); await aliceTestClient.flushSync();
}).then(function() {
logger.log("Fetching bob's devices and marking known"); logger.log("Fetching bob's devices and marking known");
aliceTestClient.httpBackend.when('POST', '/keys/query').respond( aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
200, getTestKeysQueryResponse('@bob:xyz'), 200, getTestKeysQueryResponse('@bob:xyz'),
); );
return Promise.all([ await Promise.all([
aliceTestClient.client.downloadKeys(['@bob:xyz']), aliceTestClient.client.downloadKeys(['@bob:xyz']),
aliceTestClient.httpBackend.flushAllExpected(), aliceTestClient.httpBackend.flushAllExpected(),
]).then((keys) => { ]);
aliceTestClient.client.setDeviceKnown('@bob:xyz', 'DEVICE_ID'); await aliceTestClient.client.setDeviceKnown('@bob:xyz', 'DEVICE_ID');
});
}).then(function() {
logger.log('Telling alice to send a megolm message'); logger.log('Telling alice to send a megolm message');
let megolmSessionId: string;
aliceTestClient.httpBackend.when( aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/m.room.encrypted/', 'PUT', '/sendToDevice/m.room.encrypted/',
).respond(200, function(path, content) { ).respond(200, function(_path, content) {
logger.log('sendToDevice: ', content); logger.log('sendToDevice: ', content);
const m = content.messages['@bob:xyz'].DEVICE_ID; const m = content.messages['@bob:xyz'].DEVICE_ID;
const ct = m.ciphertext[testSenderKey]; const ct = m.ciphertext[testSenderKey];
@ -676,7 +621,7 @@ describe("megolm", function() {
aliceTestClient.httpBackend.when( aliceTestClient.httpBackend.when(
'PUT', '/send/', 'PUT', '/send/',
).respond(200, function(path, content) { ).respond(200, function(_path, content) {
logger.log('/send:', content); logger.log('/send:', content);
expect(content.session_id).toEqual(megolmSessionId); expect(content.session_id).toEqual(megolmSessionId);
return { return {
@ -684,22 +629,20 @@ describe("megolm", function() {
}; };
}); });
return Promise.all([ await Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'), aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'),
// the crypto stuff can take a while, so give the requests a whole second. // the crypto stuff can take a while, so give the requests a whole second.
aliceTestClient.httpBackend.flushAllExpected({ aliceTestClient.httpBackend.flushAllExpected({ timeout: 1000 }),
timeout: 1000,
}),
]); ]);
}).then(function() {
logger.log('Telling alice to block our device'); logger.log('Telling alice to block our device');
aliceTestClient.client.setDeviceBlocked('@bob:xyz', 'DEVICE_ID'); aliceTestClient.client.setDeviceBlocked('@bob:xyz', 'DEVICE_ID');
logger.log('Telling alice to send another megolm message'); logger.log('Telling alice to send another megolm message');
aliceTestClient.httpBackend.when( aliceTestClient.httpBackend.when(
'PUT', '/send/', 'PUT', '/send/',
).respond(200, function(path, content) { ).respond(200, function(_path, content) {
logger.log('/send:', content); logger.log('/send:', content);
expect(content.session_id).not.toEqual(megolmSessionId); expect(content.session_id).not.toEqual(megolmSessionId);
return { return {
@ -710,80 +653,68 @@ describe("megolm", function() {
'PUT', '/sendToDevice/m.room_key.withheld/', 'PUT', '/sendToDevice/m.room_key.withheld/',
).respond(200, {}); ).respond(200, {});
return Promise.all([ await Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test2'), aliceTestClient.client.sendTextMessage(ROOM_ID, 'test2'),
aliceTestClient.httpBackend.flushAllExpected(), aliceTestClient.httpBackend.flushAllExpected(),
]); ]);
}); });
});
// https://github.com/vector-im/element-web/issues/2676 // https://github.com/vector-im/element-web/issues/2676
it("Alice should send to her other devices", function() { it("Alice should send to her other devices", async () => {
// for this test, we make the testOlmAccount be another of Alice's devices. // for this test, we make the testOlmAccount be another of Alice's devices.
// it ought to get included in messages Alice sends. // it ought to get included in messages Alice sends.
await aliceTestClient.start();
let p2pSession;
let inboundGroupSession;
let decrypted;
return aliceTestClient.start().then(function() {
// an encrypted room with just alice // an encrypted room with just alice
const syncResponse = { const syncResponse = {
next_batch: 1, next_batch: 1,
rooms: { rooms: { join: { [ROOM_ID]: { state: { events: [
join: {},
},
};
syncResponse.rooms.join[ROOM_ID] = {
state: {
events: [
testUtils.mkEvent({ testUtils.mkEvent({
type: 'm.room.encryption', type: 'm.room.encryption',
skey: '', skey: '',
content: { content: { algorithm: 'm.megolm.v1.aes-sha2' },
algorithm: 'm.megolm.v1.aes-sha2',
},
}), }),
testUtils.mkMembership({ testUtils.mkMembership({
mship: 'join', mship: 'join',
sender: aliceTestClient.userId, sender: aliceTestClient.userId,
}), }),
], ] } } } },
},
}; };
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse); aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
// the completion of the first initialsync hould make Alice // the completion of the first initialsync should make Alice
// invalidate the device cache for all members in e2e rooms (ie, // invalidate the device cache for all members in e2e rooms (ie,
// herself), and do a key query. // herself), and do a key query.
aliceTestClient.expectKeyQuery( aliceTestClient.expectKeyQuery(
getTestKeysQueryResponse(aliceTestClient.userId), getTestKeysQueryResponse(aliceTestClient.userId),
); );
return aliceTestClient.httpBackend.flushAllExpected(); await aliceTestClient.httpBackend.flushAllExpected();
}).then(function() {
// start out with the device unknown - the send should be rejected. // start out with the device unknown - the send should be rejected.
return aliceTestClient.client.sendTextMessage(ROOM_ID, 'test').then(() => { try {
throw new Error("sendTextMessage failed on an unknown device"); await aliceTestClient.client.sendTextMessage(ROOM_ID, 'test');
}, (e) => { throw new Error("sendTextMessage succeeded on an unknown device");
} catch (e) {
expect(e.name).toEqual("UnknownDeviceError"); expect(e.name).toEqual("UnknownDeviceError");
expect(Object.keys(e.devices)).toEqual([aliceTestClient.userId]); expect(Object.keys(e.devices)).toEqual([aliceTestClient.userId]);
expect(Object.keys(e.devices[aliceTestClient.userId])). expect(Object.keys(e.devices[aliceTestClient.userId])).
toEqual(['DEVICE_ID']); toEqual(['DEVICE_ID']);
}); }
}).then(function() {
// mark the device as known, and resend. // mark the device as known, and resend.
aliceTestClient.client.setDeviceKnown(aliceTestClient.userId, 'DEVICE_ID'); aliceTestClient.client.setDeviceKnown(aliceTestClient.userId, 'DEVICE_ID');
aliceTestClient.httpBackend.when('POST', '/keys/claim').respond( aliceTestClient.httpBackend.when('POST', '/keys/claim').respond(
200, function(path, content) { 200, function(_path, content) {
expect(content.one_time_keys[aliceTestClient.userId].DEVICE_ID) expect(content.one_time_keys[aliceTestClient.userId].DEVICE_ID)
.toEqual("signed_curve25519"); .toEqual("signed_curve25519");
return getTestKeysClaimResponse(aliceTestClient.userId); return getTestKeysClaimResponse(aliceTestClient.userId);
}); });
let p2pSession: Olm.Session;
let inboundGroupSession: Olm.InboundGroupSession;
aliceTestClient.httpBackend.when( aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/m.room.encrypted/', 'PUT', '/sendToDevice/m.room.encrypted/',
).respond(200, function(path, content) { ).respond(200, function(_path, content) {
logger.log("sendToDevice: ", content); logger.log("sendToDevice: ", content);
const m = content.messages[aliceTestClient.userId].DEVICE_ID; const m = content.messages[aliceTestClient.userId].DEVICE_ID;
const ct = m.ciphertext[testSenderKey]; const ct = m.ciphertext[testSenderKey];
@ -799,11 +730,12 @@ describe("megolm", function() {
return {}; return {};
}); });
let decrypted: IEvent;
aliceTestClient.httpBackend.when( aliceTestClient.httpBackend.when(
'PUT', '/send/', 'PUT', '/send/',
).respond(200, function(path, content) { ).respond(200, function(_path, content: IContent) {
const ct = content.ciphertext; const ct = content.ciphertext;
const r = inboundGroupSession.decrypt(ct); const r: any = inboundGroupSession.decrypt(ct);
logger.log('Decrypted received megolm message', r); logger.log('Decrypted received megolm message', r);
decrypted = JSON.parse(r.plaintext); decrypted = JSON.parse(r.plaintext);
@ -818,7 +750,7 @@ describe("megolm", function() {
expect(pendingEvents.length).toEqual(1); expect(pendingEvents.length).toEqual(1);
const unsentEvent = pendingEvents[0]; const unsentEvent = pendingEvents[0];
return Promise.all([ await Promise.all([
aliceTestClient.client.resendEvent(unsentEvent, room), aliceTestClient.client.resendEvent(unsentEvent, room),
// the crypto stuff can take a while, so give the requests a whole second. // the crypto stuff can take a while, so give the requests a whole second.
@ -826,21 +758,17 @@ describe("megolm", function() {
timeout: 1000, timeout: 1000,
}), }),
]); ]);
}).then(function() {
expect(decrypted.type).toEqual('m.room.message'); expect(decrypted.type).toEqual('m.room.message');
expect(decrypted.content.body).toEqual('test'); expect(decrypted.content.body).toEqual('test');
}); });
});
it('Alice should wait for device list to complete when sending a megolm message', function() {
let downloadPromise;
let sendPromise;
it('Alice should wait for device list to complete when sending a megolm message', async () => {
aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} }, failures: {} }); aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} }, failures: {} });
return aliceTestClient.start().then(() => { await aliceTestClient.start();
// establish an olm session with alice // establish an olm session with alice
return createOlmSession(testOlmAccount, aliceTestClient); const p2pSession = await createOlmSession(testOlmAccount, aliceTestClient);
}).then((p2pSession) => {
const syncResponse = getSyncResponse(['@bob:xyz']); const syncResponse = getSyncResponse(['@bob:xyz']);
const olmEvent = encryptOlmEvent({ const olmEvent = encryptOlmEvent({
@ -852,14 +780,14 @@ describe("megolm", function() {
syncResponse.to_device = { events: [olmEvent] }; syncResponse.to_device = { events: [olmEvent] };
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse); aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
return aliceTestClient.flushSync(); await aliceTestClient.flushSync();
}).then(function() {
// this will block // this will block
logger.log('Forcing alice to download our device keys'); logger.log('Forcing alice to download our device keys');
downloadPromise = aliceTestClient.client.downloadKeys(['@bob:xyz']); const downloadPromise = aliceTestClient.client.downloadKeys(['@bob:xyz']);
// so will this. // so will this.
sendPromise = aliceTestClient.client.sendTextMessage(ROOM_ID, 'test') const sendPromise = aliceTestClient.client.sendTextMessage(ROOM_ID, 'test')
.then(() => { .then(() => {
throw new Error("sendTextMessage failed on an unknown device"); throw new Error("sendTextMessage failed on an unknown device");
}, (e) => { }, (e) => {
@ -870,20 +798,16 @@ describe("megolm", function() {
200, getTestKeysQueryResponse('@bob:xyz'), 200, getTestKeysQueryResponse('@bob:xyz'),
); );
return aliceTestClient.httpBackend.flushAllExpected(); await aliceTestClient.httpBackend.flushAllExpected();
}).then(function() { await Promise.all([downloadPromise, sendPromise]);
return Promise.all([downloadPromise, sendPromise]);
});
}); });
it("Alice exports megolm keys and imports them to a new device", function() { it("Alice exports megolm keys and imports them to a new device", async () => {
let messageEncrypted;
aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} }, failures: {} }); aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} }, failures: {} });
return aliceTestClient.start().then(() => { await aliceTestClient.start();
// establish an olm session with alice // establish an olm session with alice
return createOlmSession(testOlmAccount, aliceTestClient); const p2pSession = await createOlmSession(testOlmAccount, aliceTestClient);
}).then((p2pSession) => {
const groupSession = new Olm.OutboundGroupSession(); const groupSession = new Olm.OutboundGroupSession();
groupSession.create(); groupSession.create();
@ -897,74 +821,56 @@ describe("megolm", function() {
}); });
// encrypt a message with the group session // encrypt a message with the group session
messageEncrypted = encryptMegolmEvent({ const messageEncrypted = encryptMegolmEvent({
senderKey: testSenderKey, senderKey: testSenderKey,
groupSession: groupSession, groupSession: groupSession,
room_id: ROOM_ID, room_id: ROOM_ID,
}); });
// Alice gets both the events in a single sync // Alice gets both the events in a single sync
const syncResponse = { aliceTestClient.httpBackend.when("GET", "/sync").respond(200, {
next_batch: 1, next_batch: 1,
to_device: { to_device: {
events: [roomKeyEncrypted], events: [roomKeyEncrypted],
}, },
rooms: { rooms: {
join: {}, join: { [ROOM_ID]: { timeline: { events: [messageEncrypted] } } },
}, },
}; });
syncResponse.rooms.join[ROOM_ID] = { await aliceTestClient.flushSync();
timeline: {
events: [messageEncrypted],
},
};
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse);
return aliceTestClient.flushSync();
}).then(async function() {
const room = aliceTestClient.client.getRoom(ROOM_ID); const room = aliceTestClient.client.getRoom(ROOM_ID);
await room.decryptCriticalEvents(); await room.decryptCriticalEvents();
const event = room.getLiveTimeline().getEvents()[0]; expect(room.getLiveTimeline().getEvents()[0].getContent().body).toEqual('42');
expect(event.getContent().body).toEqual('42');
const exported = await aliceTestClient.client.exportRoomKeys();
return aliceTestClient.client.exportRoomKeys();
}).then(function(exported) {
// start a new client // start a new client
aliceTestClient.stop(); aliceTestClient.stop();
aliceTestClient = new TestClient( aliceTestClient = new TestClient(
"@alice:localhost", "device2", "access_token2", "@alice:localhost", "device2", "access_token2",
); );
return aliceTestClient.client.initCrypto().then(() => { await aliceTestClient.client.initCrypto();
aliceTestClient.client.importRoomKeys(exported); await aliceTestClient.client.importRoomKeys(exported);
return aliceTestClient.start(); await aliceTestClient.start();
});
}).then(function() {
const syncResponse = { const syncResponse = {
next_batch: 1, next_batch: 1,
rooms: { rooms: {
join: {}, join: { [ROOM_ID]: { timeline: { events: [messageEncrypted] } } },
},
};
syncResponse.rooms.join[ROOM_ID] = {
timeline: {
events: [messageEncrypted],
}, },
}; };
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse); aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse);
return aliceTestClient.flushSync(); await aliceTestClient.flushSync();
}).then(function() {
const room = aliceTestClient.client.getRoom(ROOM_ID);
const event = room.getLiveTimeline().getEvents()[0]; const event = room.getLiveTimeline().getEvents()[0];
expect(event.getContent().body).toEqual('42'); expect(event.getContent().body).toEqual('42');
}); });
});
it("Alice receives an untrusted megolm key, only to receive the trusted one shortly after", function() { it("Alice receives an untrusted megolm key, only to receive the trusted one shortly after", async () => {
const testClient = new TestClient( const testClient = new TestClient("@alice:localhost", "device2", "access_token2");
"@alice:localhost", "device2", "access_token2",
);
const groupSession = new Olm.OutboundGroupSession(); const groupSession = new Olm.OutboundGroupSession();
groupSession.create(); groupSession.create();
const inboundGroupSession = new Olm.InboundGroupSession(); const inboundGroupSession = new Olm.InboundGroupSession();
@ -974,7 +880,7 @@ describe("megolm", function() {
groupSession: groupSession, groupSession: groupSession,
room_id: ROOM_ID, room_id: ROOM_ID,
}); });
return testClient.client.initCrypto().then(() => { await testClient.client.initCrypto();
const keys = [{ const keys = [{
room_id: ROOM_ID, room_id: ROOM_ID,
algorithm: 'm.megolm.v1.aes-sha2', algorithm: 'm.megolm.v1.aes-sha2',
@ -984,18 +890,17 @@ describe("megolm", function() {
forwarding_curve25519_key_chain: [], forwarding_curve25519_key_chain: [],
sender_claimed_keys: {}, sender_claimed_keys: {},
}]; }];
return testClient.client.importRoomKeys(keys, { untrusted: true }); await testClient.client.importRoomKeys(keys, { untrusted: true });
}).then(() => {
const event = testUtils.mkEvent({ const event1 = testUtils.mkEvent({
event: true, event: true,
...rawEvent, ...rawEvent,
room: ROOM_ID, room: ROOM_ID,
}); });
return event.attemptDecryption(testClient.client.crypto, { isRetry: true }).then(() => { await event1.attemptDecryption(testClient.client.crypto, { isRetry: true });
expect(event.isKeySourceUntrusted()).toBeTruthy(); expect(event1.isKeySourceUntrusted()).toBeTruthy();
});
}).then(() => { const event2 = testUtils.mkEvent({
const event = testUtils.mkEvent({
type: 'm.room_key', type: 'm.room_key',
content: { content: {
room_id: ROOM_ID, room_id: ROOM_ID,
@ -1006,26 +911,23 @@ describe("megolm", function() {
event: true, event: true,
}); });
// @ts-ignore - private // @ts-ignore - private
event.senderCurve25519Key = testSenderKey; event2.senderCurve25519Key = testSenderKey;
// @ts-ignore - private // @ts-ignore - private
return testClient.client.crypto.onRoomKeyEvent(event); testClient.client.crypto.onRoomKeyEvent(event2);
}).then(() => {
const event = testUtils.mkEvent({ const event3 = testUtils.mkEvent({
event: true, event: true,
...rawEvent, ...rawEvent,
room: ROOM_ID, room: ROOM_ID,
}); });
return event.attemptDecryption(testClient.client.crypto, { isRetry: true }).then(() => { await event3.attemptDecryption(testClient.client.crypto, { isRetry: true });
expect(event.isKeySourceUntrusted()).toBeFalsy(); expect(event3.isKeySourceUntrusted()).toBeFalsy();
testClient.stop(); testClient.stop();
}); });
});
});
it("Alice can decrypt a message with falsey content", function() { it("Alice can decrypt a message with falsey content", async () => {
return aliceTestClient.start().then(() => { await aliceTestClient.start();
return createOlmSession(testOlmAccount, aliceTestClient); const p2pSession = await createOlmSession(testOlmAccount, aliceTestClient);
}).then((p2pSession) => {
const groupSession = new Olm.OutboundGroupSession(); const groupSession = new Olm.OutboundGroupSession();
groupSession.create(); groupSession.create();
@ -1063,26 +965,19 @@ describe("megolm", function() {
events: [roomKeyEncrypted], events: [roomKeyEncrypted],
}, },
rooms: { rooms: {
join: {}, join: { [ROOM_ID]: { timeline: { events: [messageEncrypted] } } },
},
};
syncResponse.rooms.join[ROOM_ID] = {
timeline: {
events: [messageEncrypted],
}, },
}; };
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse); aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse);
return aliceTestClient.flushSync(); await aliceTestClient.flushSync();
}).then(function() {
const room = aliceTestClient.client.getRoom(ROOM_ID); const room = aliceTestClient.client.getRoom(ROOM_ID);
const event = room.getLiveTimeline().getEvents()[0]; const event = room.getLiveTimeline().getEvents()[0];
expect(event.isEncrypted()).toBe(true); expect(event.isEncrypted()).toBe(true);
return testUtils.awaitDecryption(event); const decryptedEvent = await testUtils.awaitDecryption(event);
}).then((event) => { expect(decryptedEvent.getRoomId()).toEqual(ROOM_ID);
expect(event.getRoomId()).toEqual(ROOM_ID); expect(decryptedEvent.getContent()).toEqual({});
expect(event.getContent()).toEqual({}); expect(decryptedEvent.getClearContent()).toBeUndefined();
expect(event.getClearContent()).toBeUndefined();
});
}); });
}); });

View File

@ -129,6 +129,21 @@ export function mkEvent(opts: IEventOpts & { event?: boolean }, client?: MatrixC
return opts.event ? new MatrixEvent(event) : event; return opts.event ? new MatrixEvent(event) : event;
} }
type GeneratedMetadata = {
event_id: string;
txn_id: string;
origin_server_ts: number;
};
export function mkEventCustom<T>(base: T): T & GeneratedMetadata {
return {
event_id: "$" + testEventIndex++ + "-" + Math.random() + "-" + Math.random(),
txn_id: "~" + Math.random(),
origin_server_ts: Date.now(),
...base,
};
}
interface IPresenceOpts { interface IPresenceOpts {
user?: string; user?: string;
sender?: string; sender?: string;
@ -208,6 +223,18 @@ export function mkMembership(opts: IMembershipOpts & { event?: boolean }): Parti
return mkEvent(eventOpts); return mkEvent(eventOpts);
} }
export function mkMembershipCustom<T>(
base: T & { membership: string, sender: string, content?: IContent },
): T & { type: EventType, sender: string, state_key: string, content: IContent } & GeneratedMetadata {
const content = base.content || {};
return mkEventCustom({
...base,
content: { ...content, membership: base.membership },
type: EventType.RoomMember,
state_key: base.sender,
});
}
interface IMessageOpts { interface IMessageOpts {
room?: string; room?: string;
user: string; user: string;