You've already forked matrix-js-sdk
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:
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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] = {};
|
||||||
|
|||||||
@@ -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(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user