Skip to content

Commit 43b4538

Browse files
authored
Always block sending keys to unverified devices of verified users (#2562)
1 parent d867aff commit 43b4538

File tree

2 files changed

+127
-2
lines changed

2 files changed

+127
-2
lines changed

spec/unit/crypto/algorithms/megolm.spec.ts

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ import * as olmlib from "../../../../src/crypto/olmlib";
3030
import { TypedEventEmitter } from '../../../../src/models/typed-event-emitter';
3131
import { ClientEvent, MatrixClient, RoomMember } from '../../../../src';
3232
import { DeviceInfo, IDevice } from '../../../../src/crypto/deviceinfo';
33-
import { DeviceTrustLevel } from '../../../../src/crypto/CrossSigning';
33+
import { DeviceTrustLevel, UserTrustLevel } from '../../../../src/crypto/CrossSigning';
34+
import { resetCrossSigningKeys } from "../crypto-utils";
3435

3536
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
3637
const MegolmEncryption = algorithms.ENCRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
@@ -344,6 +345,10 @@ describe("MegolmDecryption", function() {
344345
},
345346
}));
346347

348+
mockCrypto.checkUserTrust.mockReturnValue({
349+
isVerified: () => false,
350+
} as UserTrustLevel);
351+
347352
mockCrypto.checkDeviceTrust.mockReturnValue({
348353
isVerified: () => false,
349354
} as DeviceTrustLevel);
@@ -577,6 +582,120 @@ describe("MegolmDecryption", function() {
577582
bobClient2.stopClient();
578583
});
579584

585+
it("always blocks unverified devices of verified users", async function() {
586+
const keys = {};
587+
function getCrossSigningKey(keyType: string) {
588+
return keys[keyType];
589+
}
590+
591+
function saveCrossSigningKeys(k: Record<string, Uint8Array>) {
592+
Object.assign(keys, k);
593+
}
594+
595+
const aliceClient = (new TestClient(
596+
"@alice:example.com", "alicedevice",
597+
undefined, undefined,
598+
{ cryptoCallbacks: { getCrossSigningKey, saveCrossSigningKeys } },
599+
)).client;
600+
const bobClient1 = (new TestClient(
601+
"@bob:example.com", "bobdevice1",
602+
)).client;
603+
await Promise.all([
604+
aliceClient.initCrypto(),
605+
bobClient1.initCrypto(),
606+
]);
607+
const aliceDevice = aliceClient.crypto.olmDevice;
608+
const bobDevice1 = bobClient1.crypto.olmDevice;
609+
610+
aliceClient.uploadDeviceSigningKeys = async () => ({});
611+
aliceClient.uploadKeySignatures = async () => ({ failures: {} });
612+
// set Alice's cross-signing key
613+
await resetCrossSigningKeys(aliceClient);
614+
// Alice downloads Bob's device key
615+
aliceClient.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
616+
keys: {
617+
master: {
618+
user_id: "@bob:example.com",
619+
usage: ["master"],
620+
keys: {
621+
"ed25519:bobs+master+pubkey": "bobs+master+pubkey",
622+
},
623+
},
624+
},
625+
firstUse: false,
626+
crossSigningVerifiedBefore: false,
627+
});
628+
await aliceClient.setDeviceVerified("@bob:example.com", "bobs+master+pubkey", true);
629+
630+
const encryptionCfg = {
631+
"algorithm": "m.megolm.v1.aes-sha2",
632+
};
633+
const roomId = "!someroom";
634+
const room = new Room(roomId, aliceClient, "@alice:example.com", {});
635+
const bobMember = new RoomMember(roomId, "@bob:example.com");
636+
room.getEncryptionTargetMembers = async function() {
637+
return [bobMember];
638+
};
639+
room.setBlacklistUnverifiedDevices(false);
640+
aliceClient.store.storeRoom(room);
641+
await aliceClient.setRoomEncryption(roomId, encryptionCfg);
642+
643+
const BOB_DEVICES = {
644+
bobdevice1: {
645+
user_id: "@bob:example.com",
646+
device_id: "bobdevice1",
647+
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
648+
keys: {
649+
"ed25519:Dynabook": bobDevice1.deviceEd25519Key,
650+
"curve25519:Dynabook": bobDevice1.deviceCurve25519Key,
651+
},
652+
verified: 0,
653+
known: true,
654+
},
655+
};
656+
657+
aliceClient.crypto.deviceList.storeDevicesForUser(
658+
"@bob:example.com", BOB_DEVICES,
659+
);
660+
aliceClient.crypto.deviceList.downloadKeys = async function(userIds) {
661+
return this.getDevicesFromStore(userIds);
662+
};
663+
664+
aliceClient.sendToDevice = jest.fn().mockResolvedValue({});
665+
666+
const event = new MatrixEvent({
667+
type: "m.room.message",
668+
sender: "@alice:example.com",
669+
room_id: roomId,
670+
event_id: "$event",
671+
content: {
672+
msgtype: "m.text",
673+
body: "secret",
674+
},
675+
});
676+
await aliceClient.crypto.encryptEvent(event, room);
677+
678+
expect(aliceClient.sendToDevice).toHaveBeenCalled();
679+
const [msgtype, contentMap] = mocked(aliceClient.sendToDevice).mock.calls[0];
680+
expect(msgtype).toMatch(/^(org.matrix|m).room_key.withheld$/);
681+
delete contentMap["@bob:example.com"].bobdevice1.session_id;
682+
expect(contentMap).toStrictEqual({
683+
'@bob:example.com': {
684+
bobdevice1: {
685+
algorithm: "m.megolm.v1.aes-sha2",
686+
room_id: roomId,
687+
code: 'm.unverified',
688+
reason:
689+
'The sender has disabled encrypting to unverified devices.',
690+
sender_key: aliceDevice.deviceCurve25519Key,
691+
},
692+
},
693+
});
694+
695+
aliceClient.stopClient();
696+
bobClient1.stopClient();
697+
});
698+
580699
it("notifies devices when unable to create olm session", async function() {
581700
const aliceClient = (new TestClient(
582701
"@alice:example.com", "alicedevice",

src/crypto/algorithms/megolm.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1156,10 +1156,16 @@ class MegolmEncryption extends EncryptionAlgorithm {
11561156
continue;
11571157
}
11581158

1159+
const userTrust = this.crypto.checkUserTrust(userId);
11591160
const deviceTrust = this.crypto.checkDeviceTrust(userId, deviceId);
11601161

11611162
if (userDevices[deviceId].isBlocked() ||
1162-
(!deviceTrust.isVerified() && isBlacklisting)
1163+
(!deviceTrust.isVerified() && isBlacklisting) ||
1164+
// Always withhold keys from unverified devices of verified users
1165+
(!deviceTrust.isVerified() &&
1166+
userTrust.isVerified() &&
1167+
this.crypto.getCryptoTrustCrossSignedDevices()
1168+
)
11631169
) {
11641170
if (!blocked[userId]) {
11651171
blocked[userId] = {};

0 commit comments

Comments
 (0)