1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-25 05:23:13 +03:00

Encryption should not hinder verification (#2734)

Co-authored-by: Faye Duxovni <fayed@matrix.org>
This commit is contained in:
Valere
2022-10-18 21:56:34 +02:00
committed by GitHub
parent 0231d40277
commit 1c3dd0e51e
7 changed files with 222 additions and 109 deletions

View File

@@ -493,9 +493,9 @@ describe("MegolmDecryption", function() {
bobClient1.initCrypto(), bobClient1.initCrypto(),
bobClient2.initCrypto(), bobClient2.initCrypto(),
]); ]);
const aliceDevice = aliceClient.crypto.olmDevice; const aliceDevice = aliceClient.crypto!.olmDevice;
const bobDevice1 = bobClient1.crypto.olmDevice; const bobDevice1 = bobClient1.crypto!.olmDevice;
const bobDevice2 = bobClient2.crypto.olmDevice; const bobDevice2 = bobClient2.crypto!.olmDevice;
const encryptionCfg = { const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2", "algorithm": "m.megolm.v1.aes-sha2",
@@ -532,10 +532,10 @@ describe("MegolmDecryption", function() {
}, },
}; };
aliceClient.crypto.deviceList.storeDevicesForUser( aliceClient.crypto!.deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES, "@bob:example.com", BOB_DEVICES,
); );
aliceClient.crypto.deviceList.downloadKeys = async function(userIds) { aliceClient.crypto!.deviceList.downloadKeys = async function(userIds) {
return this.getDevicesFromStore(userIds); return this.getDevicesFromStore(userIds);
}; };
@@ -551,7 +551,7 @@ describe("MegolmDecryption", function() {
body: "secret", body: "secret",
}, },
}); });
await aliceClient.crypto.encryptEvent(event, room); await aliceClient.crypto!.encryptEvent(event, room);
expect(aliceClient.sendToDevice).toHaveBeenCalled(); expect(aliceClient.sendToDevice).toHaveBeenCalled();
const [msgtype, contentMap] = mocked(aliceClient.sendToDevice).mock.calls[0]; const [msgtype, contentMap] = mocked(aliceClient.sendToDevice).mock.calls[0];
@@ -583,6 +583,100 @@ describe("MegolmDecryption", function() {
bobClient2.stopClient(); bobClient2.stopClient();
}); });
it("does not block unverified devices when sending verification events", async function() {
const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const bobClient = (new TestClient(
"@bob:example.com", "bobdevice",
)).client;
await Promise.all([
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const bobDevice = bobClient.crypto!.olmDevice;
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
};
const roomId = "!someroom";
const room = new Room(roomId, aliceClient, "@alice:example.com", {});
const bobMember = new RoomMember(roomId, "@bob:example.com");
room.getEncryptionTargetMembers = async function() {
return [bobMember];
};
room.setBlacklistUnverifiedDevices(true);
aliceClient.store.storeRoom(room);
await aliceClient.setRoomEncryption(roomId, encryptionCfg);
const BOB_DEVICES: Record<string, IDevice> = {
bobdevice: {
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:bobdevice": bobDevice.deviceEd25519Key,
"curve25519:bobdevice": bobDevice.deviceCurve25519Key,
},
verified: 0,
known: true,
},
};
aliceClient.crypto!.deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
aliceClient.crypto!.deviceList.downloadKeys = async function(userIds) {
// @ts-ignore private
return this.getDevicesFromStore(userIds);
};
await bobDevice.generateOneTimeKeys(1);
const oneTimeKeys = await bobDevice.getOneTimeKeys();
const signedOneTimeKeys: Record<string, { key: string, signatures: object }> = {};
for (const keyId in oneTimeKeys.curve25519) {
if (oneTimeKeys.curve25519.hasOwnProperty(keyId)) {
const k = {
key: oneTimeKeys.curve25519[keyId],
signatures: {},
};
signedOneTimeKeys["signed_curve25519:" + keyId] = k;
await bobClient.crypto!.signObject(k);
break;
}
}
aliceClient.claimOneTimeKeys = jest.fn().mockResolvedValue({
one_time_keys: {
'@bob:example.com': {
bobdevice: signedOneTimeKeys,
},
},
failures: {},
});
aliceClient.sendToDevice = jest.fn().mockResolvedValue({});
const event = new MatrixEvent({
type: "m.key.verification.start",
sender: "@alice:example.com",
room_id: roomId,
event_id: "$event",
content: {
from_device: "alicedevice",
method: "m.sas.v1",
transaction_id: "transactionid",
},
});
await aliceClient.crypto!.encryptEvent(event, room);
expect(aliceClient.sendToDevice).toHaveBeenCalled();
const [msgtype] = mocked(aliceClient.sendToDevice).mock.calls[0];
expect(msgtype).toEqual("m.room.encrypted");
aliceClient.stopClient();
bobClient.stopClient();
});
it("notifies devices when unable to create olm session", async function() { it("notifies devices when unable to create olm session", async function() {
const aliceClient = (new TestClient( const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice", "@alice:example.com", "alicedevice",
@@ -594,8 +688,8 @@ describe("MegolmDecryption", function() {
aliceClient.initCrypto(), aliceClient.initCrypto(),
bobClient.initCrypto(), bobClient.initCrypto(),
]); ]);
const aliceDevice = aliceClient.crypto.olmDevice; const aliceDevice = aliceClient.crypto!.olmDevice;
const bobDevice = bobClient.crypto.olmDevice; const bobDevice = bobClient.crypto!.olmDevice;
const encryptionCfg = { const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2", "algorithm": "m.megolm.v1.aes-sha2",
@@ -632,10 +726,11 @@ describe("MegolmDecryption", function() {
}, },
}; };
aliceClient.crypto.deviceList.storeDevicesForUser( aliceClient.crypto!.deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES, "@bob:example.com", BOB_DEVICES,
); );
aliceClient.crypto.deviceList.downloadKeys = async function(userIds) { aliceClient.crypto!.deviceList.downloadKeys = async function(userIds) {
// @ts-ignore private
return this.getDevicesFromStore(userIds); return this.getDevicesFromStore(userIds);
}; };
@@ -654,7 +749,7 @@ describe("MegolmDecryption", function() {
event_id: "$event", event_id: "$event",
content: {}, content: {},
}); });
await aliceClient.crypto.encryptEvent(event, aliceRoom); await aliceClient.crypto!.encryptEvent(event, aliceRoom);
expect(aliceClient.sendToDevice).toHaveBeenCalled(); expect(aliceClient.sendToDevice).toHaveBeenCalled();
const [msgtype, contentMap] = mocked(aliceClient.sendToDevice).mock.calls[0]; const [msgtype, contentMap] = mocked(aliceClient.sendToDevice).mock.calls[0];
@@ -685,10 +780,10 @@ describe("MegolmDecryption", function() {
aliceClient.initCrypto(), aliceClient.initCrypto(),
bobClient.initCrypto(), bobClient.initCrypto(),
]); ]);
const bobDevice = bobClient.crypto.olmDevice; const bobDevice = bobClient.crypto!.olmDevice;
const aliceEventEmitter = new TypedEventEmitter<ClientEvent.ToDeviceEvent, any>(); const aliceEventEmitter = new TypedEventEmitter<ClientEvent.ToDeviceEvent, any>();
aliceClient.crypto.registerEventHandlers(aliceEventEmitter); aliceClient.crypto!.registerEventHandlers(aliceEventEmitter);
const roomId = "!someroom"; const roomId = "!someroom";
@@ -705,7 +800,7 @@ describe("MegolmDecryption", function() {
}, },
})); }));
await expect(aliceClient.crypto.decryptEvent(new MatrixEvent({ await expect(aliceClient.crypto!.decryptEvent(new MatrixEvent({
type: "m.room.encrypted", type: "m.room.encrypted",
sender: "@bob:example.com", sender: "@bob:example.com",
event_id: "$event", event_id: "$event",
@@ -732,7 +827,7 @@ describe("MegolmDecryption", function() {
}, },
})); }));
await expect(aliceClient.crypto.decryptEvent(new MatrixEvent({ await expect(aliceClient.crypto!.decryptEvent(new MatrixEvent({
type: "m.room.encrypted", type: "m.room.encrypted",
sender: "@bob:example.com", sender: "@bob:example.com",
event_id: "$event", event_id: "$event",
@@ -762,10 +857,10 @@ describe("MegolmDecryption", function() {
]); ]);
const aliceEventEmitter = new TypedEventEmitter<ClientEvent.ToDeviceEvent, any>(); const aliceEventEmitter = new TypedEventEmitter<ClientEvent.ToDeviceEvent, any>();
aliceClient.crypto.registerEventHandlers(aliceEventEmitter); aliceClient.crypto!.registerEventHandlers(aliceEventEmitter);
aliceClient.crypto.downloadKeys = jest.fn(); aliceClient.crypto!.downloadKeys = jest.fn();
const bobDevice = bobClient.crypto.olmDevice; const bobDevice = bobClient.crypto!.olmDevice;
const roomId = "!someroom"; const roomId = "!someroom";
@@ -788,7 +883,7 @@ describe("MegolmDecryption", function() {
setTimeout(resolve, 100); setTimeout(resolve, 100);
}); });
await expect(aliceClient.crypto.decryptEvent(new MatrixEvent({ await expect(aliceClient.crypto!.decryptEvent(new MatrixEvent({
type: "m.room.encrypted", type: "m.room.encrypted",
sender: "@bob:example.com", sender: "@bob:example.com",
event_id: "$event", event_id: "$event",
@@ -820,7 +915,7 @@ describe("MegolmDecryption", function() {
setTimeout(resolve, 100); setTimeout(resolve, 100);
}); });
await expect(aliceClient.crypto.decryptEvent(new MatrixEvent({ await expect(aliceClient.crypto!.decryptEvent(new MatrixEvent({
type: "m.room.encrypted", type: "m.room.encrypted",
sender: "@bob:example.com", sender: "@bob:example.com",
event_id: "$event", event_id: "$event",
@@ -850,10 +945,10 @@ describe("MegolmDecryption", function() {
bobClient.initCrypto(), bobClient.initCrypto(),
]); ]);
const aliceEventEmitter = new TypedEventEmitter<ClientEvent.ToDeviceEvent, any>(); const aliceEventEmitter = new TypedEventEmitter<ClientEvent.ToDeviceEvent, any>();
aliceClient.crypto.registerEventHandlers(aliceEventEmitter); aliceClient.crypto!.registerEventHandlers(aliceEventEmitter);
const bobDevice = bobClient.crypto.olmDevice; const bobDevice = bobClient.crypto!.olmDevice;
aliceClient.crypto.downloadKeys = jest.fn(); aliceClient.crypto!.downloadKeys = jest.fn();
const roomId = "!someroom"; const roomId = "!someroom";
@@ -875,7 +970,7 @@ describe("MegolmDecryption", function() {
setTimeout(resolve, 100); setTimeout(resolve, 100);
}); });
await expect(aliceClient.crypto.decryptEvent(new MatrixEvent({ await expect(aliceClient.crypto!.decryptEvent(new MatrixEvent({
type: "m.room.encrypted", type: "m.room.encrypted",
sender: "@bob:example.com", sender: "@bob:example.com",
event_id: "$event", event_id: "$event",

View File

@@ -59,6 +59,10 @@ export enum EventType {
KeyVerificationCancel = "m.key.verification.cancel", KeyVerificationCancel = "m.key.verification.cancel",
KeyVerificationMac = "m.key.verification.mac", KeyVerificationMac = "m.key.verification.mac",
KeyVerificationDone = "m.key.verification.done", KeyVerificationDone = "m.key.verification.done",
KeyVerificationKey = "m.key.verification.key",
KeyVerificationAccept = "m.key.verification.accept",
// XXX this event is not yet supported by js-sdk
KeyVerificationReady = "m.key.verification.ready",
// use of this is discouraged https://matrix.org/docs/spec/client_server/r0.6.1#m-room-message-feedback // use of this is discouraged https://matrix.org/docs/spec/client_server/r0.6.1#m-room-message-feedback
RoomMessageFeedback = "m.room.message.feedback", RoomMessageFeedback = "m.room.message.feedback",
Reaction = "m.reaction", Reaction = "m.reaction",

View File

@@ -36,6 +36,7 @@ import { DeviceInfo } from "../deviceinfo";
import { IOlmSessionResult } from "../olmlib"; import { IOlmSessionResult } from "../olmlib";
import { DeviceInfoMap } from "../DeviceList"; import { DeviceInfoMap } from "../DeviceList";
import { MatrixEvent } from "../../models/event"; import { MatrixEvent } from "../../models/event";
import { EventType, MsgType } from '../../@types/event';
import { IEventDecryptionResult, IMegolmSessionData, IncomingRoomKeyRequest } from "../index"; import { IEventDecryptionResult, IMegolmSessionData, IncomingRoomKeyRequest } from "../index";
import { RoomKeyRequestState } from '../OutgoingRoomKeyRequestManager'; import { RoomKeyRequestState } from '../OutgoingRoomKeyRequestManager';
import { OlmGroupSessionExtraData } from "../../@types/crypto"; import { OlmGroupSessionExtraData } from "../../@types/crypto";
@@ -1019,7 +1020,12 @@ class MegolmEncryption extends EncryptionAlgorithm {
} }
} }
const [devicesInRoom, blocked] = await this.getDevicesInRoom(room); /**
* When using in-room messages and the room has encryption enabled,
* clients should ensure that encryption does not hinder the verification.
*/
const forceDistributeToUnverified = this.isVerificationEvent(eventType, content);
const [devicesInRoom, blocked] = await this.getDevicesInRoom(room, forceDistributeToUnverified);
// check if any of these devices are not yet known to the user. // check if any of these devices are not yet known to the user.
// if so, warn the user so they can verify or ignore. // if so, warn the user so they can verify or ignore.
@@ -1053,6 +1059,26 @@ class MegolmEncryption extends EncryptionAlgorithm {
return encryptedContent; return encryptedContent;
} }
private isVerificationEvent(eventType: string, content: object): boolean {
switch (eventType) {
case EventType.KeyVerificationCancel:
case EventType.KeyVerificationDone:
case EventType.KeyVerificationMac:
case EventType.KeyVerificationStart:
case EventType.KeyVerificationKey:
case EventType.KeyVerificationReady:
case EventType.KeyVerificationAccept: {
return true;
}
case EventType.RoomMessage: {
return content['msgtype'] === MsgType.KeyVerificationRequest;
}
default: {
return false;
}
}
}
/** /**
* Forces the current outbound group session to be discarded such * Forces the current outbound group session to be discarded such
* that another one will be created next time an event is sent. * that another one will be created next time an event is sent.
@@ -1119,6 +1145,8 @@ class MegolmEncryption extends EncryptionAlgorithm {
* Get the list of unblocked devices for all users in the room * Get the list of unblocked devices for all users in the room
* *
* @param {module:models/room} room * @param {module:models/room} room
* @param forceDistributeToUnverified if set to true will include the unverified devices
* even if setting is set to block them (useful for verification)
* *
* @return {Promise} Promise which resolves to an array whose * @return {Promise} Promise which resolves to an array whose
* first element is a map from userId to deviceId to deviceInfo indicating * first element is a map from userId to deviceId to deviceInfo indicating
@@ -1126,7 +1154,10 @@ class MegolmEncryption extends EncryptionAlgorithm {
* element is a map from userId to deviceId to data indicating the devices * element is a map from userId to deviceId to data indicating the devices
* that are in the room but that have been blocked * that are in the room but that have been blocked
*/ */
private async getDevicesInRoom(room: Room): Promise<[DeviceInfoMap, IBlockedMap]> { private async getDevicesInRoom(
room: Room,
forceDistributeToUnverified = false,
): Promise<[DeviceInfoMap, IBlockedMap]> {
const members = await room.getEncryptionTargetMembers(); const members = await room.getEncryptionTargetMembers();
const roomMembers = members.map(function(u) { const roomMembers = members.map(function(u) {
return u.userId; return u.userId;
@@ -1161,7 +1192,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
const deviceTrust = this.crypto.checkDeviceTrust(userId, deviceId); const deviceTrust = this.crypto.checkDeviceTrust(userId, deviceId);
if (userDevices[deviceId].isBlocked() || if (userDevices[deviceId].isBlocked() ||
(!deviceTrust.isVerified() && isBlacklisting) (!deviceTrust.isVerified() && isBlacklisting && !forceDistributeToUnverified)
) { ) {
if (!blocked[userId]) { if (!blocked[userId]) {
blocked[userId] = {}; blocked[userId] = {};

View File

@@ -21,6 +21,7 @@ limitations under the License.
*/ */
import { MatrixEvent } from '../../models/event'; import { MatrixEvent } from '../../models/event';
import { EventType } from '../../@types/event';
import { logger } from '../../logger'; import { logger } from '../../logger';
import { DeviceInfo } from '../deviceinfo'; import { DeviceInfo } from '../deviceinfo';
import { newTimeoutError } from "./Error"; import { newTimeoutError } from "./Error";
@@ -182,13 +183,13 @@ export class VerificationBase<
} else if (e.getType() === this.expectedEvent) { } else if (e.getType() === this.expectedEvent) {
// if we receive an expected m.key.verification.done, then just // if we receive an expected m.key.verification.done, then just
// ignore it, since we don't need to do anything about it // ignore it, since we don't need to do anything about it
if (this.expectedEvent !== "m.key.verification.done") { if (this.expectedEvent !== EventType.KeyVerificationDone) {
this.expectedEvent = undefined; this.expectedEvent = undefined;
this.rejectEvent = undefined; this.rejectEvent = undefined;
this.resetTimer(); this.resetTimer();
this.resolveEvent(e); this.resolveEvent(e);
} }
} else if (e.getType() === "m.key.verification.cancel") { } else if (e.getType() === EventType.KeyVerificationCancel) {
const reject = this.reject; const reject = this.reject;
this.reject = undefined; this.reject = undefined;
// there is only promise to reject if verify has been called // there is only promise to reject if verify has been called
@@ -241,20 +242,20 @@ export class VerificationBase<
const sender = e.getSender(); const sender = e.getSender();
if (sender !== this.userId) { if (sender !== this.userId) {
const content = e.getContent(); const content = e.getContent();
if (e.getType() === "m.key.verification.cancel") { if (e.getType() === EventType.KeyVerificationCancel) {
content.code = content.code || "m.unknown"; content.code = content.code || "m.unknown";
content.reason = content.reason || content.body content.reason = content.reason || content.body
|| "Unknown reason"; || "Unknown reason";
this.send("m.key.verification.cancel", content); this.send(EventType.KeyVerificationCancel, content);
} else { } else {
this.send("m.key.verification.cancel", { this.send(EventType.KeyVerificationCancel, {
code: "m.unknown", code: "m.unknown",
reason: content.body || "Unknown reason", reason: content.body || "Unknown reason",
}); });
} }
} }
} else { } else {
this.send("m.key.verification.cancel", { this.send(EventType.KeyVerificationCancel, {
code: "m.unknown", code: "m.unknown",
reason: e.toString(), reason: e.toString(),
}); });

View File

@@ -21,11 +21,12 @@ limitations under the License.
*/ */
import { MatrixEvent } from "../../models/event"; import { MatrixEvent } from "../../models/event";
import { EventType } from '../../@types/event';
export function newVerificationError(code: string, reason: string, extraData: Record<string, any>): MatrixEvent { export function newVerificationError(code: string, reason: string, extraData: Record<string, any>): MatrixEvent {
const content = Object.assign({}, { code, reason }, extraData); const content = Object.assign({}, { code, reason }, extraData);
return new MatrixEvent({ return new MatrixEvent({
type: "m.key.verification.cancel", type: EventType.KeyVerificationCancel,
content, content,
}); });
} }

View File

@@ -32,13 +32,14 @@ import {
} from './Error'; } from './Error';
import { logger } from '../../logger'; import { logger } from '../../logger';
import { IContent, MatrixEvent } from "../../models/event"; import { IContent, MatrixEvent } from "../../models/event";
import { EventType } from '../../@types/event';
const START_TYPE = "m.key.verification.start"; const START_TYPE = EventType.KeyVerificationStart;
const EVENTS = [ const EVENTS = [
"m.key.verification.accept", EventType.KeyVerificationAccept,
"m.key.verification.key", EventType.KeyVerificationKey,
"m.key.verification.mac", EventType.KeyVerificationMac,
]; ];
let olmutil: Utility; let olmutil: Utility;
@@ -310,6 +311,45 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
return startContent; return startContent;
} }
private async verifyAndCheckMAC(
keyAgreement: string,
sasMethods: string[],
olmSAS: OlmSAS,
macMethod: string,
): Promise<void> {
const sasBytes = calculateKeyAgreement[keyAgreement](this, olmSAS, 6);
const verifySAS = new Promise<void>((resolve, reject) => {
this.sasEvent = {
sas: generateSas(sasBytes, sasMethods),
confirm: async () => {
try {
await this.sendMAC(olmSAS, macMethod);
resolve();
} catch (err) {
reject(err);
}
},
cancel: () => reject(newUserCancelledError()),
mismatch: () => reject(newMismatchedSASError()),
};
this.emit(SasEvent.ShowSas, this.sasEvent);
});
const [e] = await Promise.all([
this.waitForEvent(EventType.KeyVerificationMac)
.then((e) => {
// we don't expect any more messages from the other
// party, and they may send a m.key.verification.done
// when they're done on their end
this.expectedEvent = EventType.KeyVerificationDone;
return e;
}),
verifySAS,
]);
const content = e.getContent();
await this.checkMAC(olmSAS, content, macMethod);
}
private async doSendVerification(): Promise<void> { private async doSendVerification(): Promise<void> {
this.waitingForAccept = true; this.waitingForAccept = true;
let startContent; let startContent;
@@ -329,7 +369,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
let e; let e;
try { try {
e = await this.waitForEvent("m.key.verification.accept"); e = await this.waitForEvent(EventType.KeyVerificationAccept);
} finally { } finally {
this.waitingForAccept = false; this.waitingForAccept = false;
} }
@@ -351,11 +391,11 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
const olmSAS = new global.Olm.SAS(); const olmSAS = new global.Olm.SAS();
try { try {
this.ourSASPubKey = olmSAS.get_pubkey(); this.ourSASPubKey = olmSAS.get_pubkey();
await this.send("m.key.verification.key", { await this.send(EventType.KeyVerificationKey, {
key: this.ourSASPubKey, key: this.ourSASPubKey,
}); });
e = await this.waitForEvent("m.key.verification.key"); e = await this.waitForEvent(EventType.KeyVerificationKey);
// FIXME: make sure event is properly formed // FIXME: make sure event is properly formed
content = e.getContent(); content = e.getContent();
const commitmentStr = content.key + anotherjson.stringify(startContent); const commitmentStr = content.key + anotherjson.stringify(startContent);
@@ -366,37 +406,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
this.theirSASPubKey = content.key; this.theirSASPubKey = content.key;
olmSAS.set_their_key(content.key); olmSAS.set_their_key(content.key);
const sasBytes = calculateKeyAgreement[keyAgreement](this, olmSAS, 6); await this.verifyAndCheckMAC(keyAgreement, sasMethods, olmSAS, macMethod);
const verifySAS = new Promise<void>((resolve, reject) => {
this.sasEvent = {
sas: generateSas(sasBytes, sasMethods),
confirm: async () => {
try {
await this.sendMAC(olmSAS, macMethod);
resolve();
} catch (err) {
reject(err);
}
},
cancel: () => reject(newUserCancelledError()),
mismatch: () => reject(newMismatchedSASError()),
};
this.emit(SasEvent.ShowSas, this.sasEvent);
});
[e] = await Promise.all([
this.waitForEvent("m.key.verification.mac")
.then((e) => {
// we don't expect any more messages from the other
// party, and they may send a m.key.verification.done
// when they're done on their end
this.expectedEvent = "m.key.verification.done";
return e;
}),
verifySAS,
]);
content = e.getContent();
await this.checkMAC(olmSAS, content, macMethod);
} finally { } finally {
olmSAS.free(); olmSAS.free();
} }
@@ -423,7 +433,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
const olmSAS = new global.Olm.SAS(); const olmSAS = new global.Olm.SAS();
try { try {
const commitmentStr = olmSAS.get_pubkey() + anotherjson.stringify(content); const commitmentStr = olmSAS.get_pubkey() + anotherjson.stringify(content);
await this.send("m.key.verification.accept", { await this.send(EventType.KeyVerificationAccept, {
key_agreement_protocol: keyAgreement, key_agreement_protocol: keyAgreement,
hash: hashMethod, hash: hashMethod,
message_authentication_code: macMethod, message_authentication_code: macMethod,
@@ -432,47 +442,17 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
commitment: olmutil.sha256(commitmentStr), commitment: olmutil.sha256(commitmentStr),
}); });
let e = await this.waitForEvent("m.key.verification.key"); const e = await this.waitForEvent(EventType.KeyVerificationKey);
// FIXME: make sure event is properly formed // FIXME: make sure event is properly formed
content = e.getContent(); content = e.getContent();
this.theirSASPubKey = content.key; this.theirSASPubKey = content.key;
olmSAS.set_their_key(content.key); olmSAS.set_their_key(content.key);
this.ourSASPubKey = olmSAS.get_pubkey(); this.ourSASPubKey = olmSAS.get_pubkey();
await this.send("m.key.verification.key", { await this.send(EventType.KeyVerificationKey, {
key: this.ourSASPubKey, key: this.ourSASPubKey,
}); });
const sasBytes = calculateKeyAgreement[keyAgreement](this, olmSAS, 6); await this.verifyAndCheckMAC(keyAgreement, sasMethods, olmSAS, macMethod);
const verifySAS = new Promise<void>((resolve, reject) => {
this.sasEvent = {
sas: generateSas(sasBytes, sasMethods),
confirm: async () => {
try {
await this.sendMAC(olmSAS, macMethod);
resolve();
} catch (err) {
reject(err);
}
},
cancel: () => reject(newUserCancelledError()),
mismatch: () => reject(newMismatchedSASError()),
};
this.emit(SasEvent.ShowSas, this.sasEvent);
});
[e] = await Promise.all([
this.waitForEvent("m.key.verification.mac")
.then((e) => {
// we don't expect any more messages from the other
// party, and they may send a m.key.verification.done
// when they're done on their end
this.expectedEvent = "m.key.verification.done";
return e;
}),
verifySAS,
]);
content = e.getContent();
await this.checkMAC(olmSAS, content, macMethod);
} finally { } finally {
olmSAS.free(); olmSAS.free();
} }
@@ -480,7 +460,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
private sendMAC(olmSAS: OlmSAS, method: string): Promise<void> { private sendMAC(olmSAS: OlmSAS, method: string): Promise<void> {
const mac = {}; const mac = {};
const keyList = []; const keyList: string[] = [];
const baseInfo = "MATRIX_KEY_VERIFICATION_MAC" const baseInfo = "MATRIX_KEY_VERIFICATION_MAC"
+ this.baseApis.getUserId() + this.baseApis.deviceId + this.baseApis.getUserId() + this.baseApis.deviceId
+ this.userId + this.deviceId + this.userId + this.deviceId
@@ -507,7 +487,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
keyList.sort().join(","), keyList.sort().join(","),
baseInfo + "KEY_IDS", baseInfo + "KEY_IDS",
); );
return this.send("m.key.verification.mac", { mac, keys }); return this.send(EventType.KeyVerificationMac, { mac, keys });
} }
private async checkMAC(olmSAS: OlmSAS, content: IContent, method: string): Promise<void> { private async checkMAC(olmSAS: OlmSAS, content: IContent, method: string): Promise<void> {

View File

@@ -25,6 +25,7 @@ import { QRCodeData, SCAN_QR_CODE_METHOD } from "../QRCode";
import { IVerificationChannel } from "./Channel"; import { IVerificationChannel } from "./Channel";
import { MatrixClient } from "../../../client"; import { MatrixClient } from "../../../client";
import { MatrixEvent } from "../../../models/event"; import { MatrixEvent } from "../../../models/event";
import { EventType } from '../../../@types/event';
import { VerificationBase } from "../Base"; import { VerificationBase } from "../Base";
import { VerificationMethod } from "../../index"; import { VerificationMethod } from "../../index";
import { TypedEventEmitter } from "../../../models/typed-event-emitter"; import { TypedEventEmitter } from "../../../models/typed-event-emitter";
@@ -931,7 +932,7 @@ export class VerificationRequest<
} }
public onVerifierFinished(): void { public onVerifierFinished(): void {
this.channel.send("m.key.verification.done", {}); this.channel.send(EventType.KeyVerificationDone, {});
this.verifierHasFinished = true; this.verifierHasFinished = true;
// move to .done phase // move to .done phase
const newTransitions = this.applyPhaseTransitions(); const newTransitions = this.applyPhaseTransitions();