CVE-2022-39250 (TypeScript, published 2022-09-29T13:15Z)
Description: value: Matrix JavaScript SDK is the Matrix Client-Server software development kit (SDK) for JavaScript. Prior to version 19.7.0, an attacker cooperating with a malicious homeserver could interfere with the verification flow between two users, injecting its own cross-signing user identity in place of one of the users’ identities. This would lead to the other device trusting/verifying the user identity under the control of the homeserver instead of the intended one. The vulnerability is a bug in the matrix-js-sdk, caused by checking and signing user identities and devices in two separate steps, and inadequately fixing the keys to be signed between those steps. Even though the attack is partly made possible due to the design decision of treating cross-signing user identities as Matrix devices on the server side (with their device ID set to the public part of the user identity key), no other examined implementations were vulnerable. Starting with version 19.7.0, the matrix-js-sdk has been modified to double check that the key signed is the one that was verified instead of just referencing the key by ID. An additional check has been made to report an error when one of the device ID matches a cross-signing key. As this attack requires coordination between a malicious homeserver and an attacker, those who trust their homeservers do not need a particular workaround.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-287 \u2014 Improper Authentication
When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
Repository: https://github.com/matrix-org/matrix-js-sdk
Files changed:
spec/integ/matrix-client-crypto.spec.ts
spec/integ/matrix-client-syncing.spec.ts
spec/integ/megolm-integ.spec.ts
spec/unit/content-helpers.spec.ts
spec/unit/crypto.spec.ts
spec/unit/crypto/algorithms/megolm.spec.ts
spec/unit/crypto/backup.spec.ts
spec/unit/crypto/secrets.spec.ts
spec/unit/crypto/verification/sas.spec.ts
spec/unit/models/beacon.spec.ts
src/@types/crypto.ts
src/NamespacedValue.ts
src/client.ts
src/content-helpers.ts
src/crypto/OlmDevice.ts
src/crypto/SecretStorage.ts
src/crypto/algorithms/megolm.ts
src/crypto/algorithms/olm.ts
src/crypto/backup.ts
src/crypto/index.ts
src/crypto/olmlib.ts
src/crypto/store/base.ts
src/crypto/store/indexeddb-crypto-store-backend.ts
src/crypto/store/indexeddb-crypto-store.ts
src/crypto/store/memory-crypto-store.ts
src/crypto/verification/Base.ts
src/event-mapper.ts
src/models/beacon.ts
src/models/event.ts
src/sync.ts
Commit message: Resolve multiple CVEs

CVE-2022-39249
CVE-2022-39250
CVE-2022-39251
CVE-2022-39236


Vulnerable code:
            aliceTestClient.httpBackend.flush('/keys/query', 1),
            algorithm: olmlib.MEGOLM_ALGORITHM,
            room_id: roomId,
            sender_key: eventContent.sender_key,
            sender_claimed_ed25519_key: key.sender_claimed_ed25519_key,
            session_id: eventContent.session_id,
            session_key: key.key,
            chain_index: key.chain_index,
            forwarding_curve25519_key_chain:
            key.forwarding_curve_key_chain,
            event.isKeySourceUntrusted = () => false;
        it("does not cancel keyshare requests if some messages are not decrypted", async function() {
            // the room key request should be gone since we've now decrypted everything
        const secret = await request.promise;
        expect(secret).toBe("bar");
            alice.client.setDeviceVerified = jest.fn();
            bob.client.setDeviceVerified = jest.fn();
            expect(alice.client.setDeviceVerified)
                .toHaveBeenCalledWith(bob.client.getUserId(), bob.client.deviceId);
            expect(bob.client.setDeviceVerified)
                .toHaveBeenCalledWith(alice.client.getUserId(), alice.client.deviceId);
Copyright 2021 The Matrix.org Foundation C.I.C.
    public findIn<T>(obj: any): T {
    timestamp: number;
    const { description, uri } = M_LOCATION.findIn<MLocationContent>(content);
        description,
        uri,
        extraSessionData: Record<string, any> = {},
                                if (existingSession.first_known_index()
                                    <= session.first_known_index()
                                    && !(existingSession.first_known_index() == session.first_known_index()
                                        && !extraSessionData.untrusted
                                        && existingSessionData.untrusted)) {
                                    // existing session has lower index (i.e. can
                                    // decrypt more), or they have the same index and
                                    // the new sessions trust does not win over the old
                                    // sessions trust, so keep it
                                    logger.log(`Keeping existing megolm session ${sessionId}`);
                                    return;
                            "forwarding_curve25519_key_chain":
                                sessionData.forwardingCurve25519KeyChain || [],
import { MatrixEvent } from "../..";
    // events which we couldn't decrypt due to unknown sessions / indexes: map from
    // senderKey|sessionId to Set of MatrixEvents
        // success. We can remove the event from the pending list, if that hasn't
        // already happened.
        this.removeEventFromPendingList(event);
        if (!senderKey) {
            logger.error("key event has no sender key (not encrypted?)");
            senderKey = content.sender_key;
        const extraSessionData: any = {};
            if (await this.retryDecryption(senderKey, content.session_id)) {
        const extraSessionData: any = {};
            this.retryDecryption(session.sender_key, session.session_id);
     * @return {Boolean} whether all messages were successfully decrypted
    private async retryDecryption(senderKey: string, sessionId: string): Promise<boolean> {
                await ev.attemptDecryption(this.crypto, { isRetry: true });
        // If decrypted successfully, they'll have been removed from pendingEvents
        ret.usable = ret.usable || ret.trusted_locally;
        const forwardingChain = event.getForwardingCurve25519KeyChain();
        if (forwardingChain.length > 0) {
            // we got the key this event from somewhere else
            // TODO: check if we can trust the forwarders.
            return null;
        }

        const forwardingChain = event.getForwardingCurve25519KeyChain();
        if (forwardingChain.length > 0 || event.isKeySourceUntrusted()) {
            Promise.resolve(this.doVerification()).then(this.done.bind(this), this.cancel.bind(this));
        const verifiedDevices = [];
                verifiedDevices.push(deviceId);
                    verifiedDevices.push(deviceId);
        for (const deviceId of verifiedDevices) {
            await this.baseApis.setDeviceVerified(userId, deviceId);
import { M_TIMESTAMP } from "../@types/location";
            const timestamp = M_TIMESTAMP.findIn<number>(content);
        if (this.clearEvent && !this.isDecryptionFailure()) {
                .map(client.getEventMapper())
            await this.processRoomEvents(room, stateEvents, events, syncEventData.fromCache);

Fixed code:
        bobTestClient.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
        bobTestClient.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
        bobTestClient.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
        bobTestClient.httpBackend.when('POST', '/keys/query').respond(
            200, {},
        );
            await client.initCrypto();

    IndexedDBCryptoStore,
    Room,
import { DeviceInfo } from "../../src/crypto/deviceinfo";
        aliceTestClient.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
        aliceTestClient.client.crypto.deviceList.getUserByIdentityKey = () => "@bob:xyz";

        aliceTestClient.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
        aliceTestClient.client.crypto.deviceList.getUserByIdentityKey = () => "@bob:xyz";

        aliceTestClient.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
        aliceTestClient.client.crypto.deviceList.getUserByIdentityKey = () => "@bob:xyz";

        aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
            200, getTestKeysQueryResponse('@bob:xyz'),
        );
        aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
            200, getTestKeysQueryResponse('@bob:xyz'),
        );
            aliceTestClient.httpBackend.flush('/keys/query', 2),
        aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
            200, getTestKeysQueryResponse('@bob:xyz'),
        );
        aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
            200, getTestKeysQueryResponse('@bob:xyz'),
        );

        aliceTestClient.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
        aliceTestClient.client.crypto.deviceList.getUserByIdentityKey = () => "@bob:xyz";

        aliceTestClient.client.crypto.deviceList.getUserByIdentityKey = () => "@bob:xyz";

        aliceTestClient.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
        aliceTestClient.client.crypto.deviceList.getUserByIdentityKey = () => "@bob:xyz";

            aliceTestClient.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
            aliceTestClient.client.crypto.deviceList.getUserByIdentityKey = () => "@bob:xyz";


    it("Alice receives shared history before being invited to a room by the sharer", async () => {
        const beccaTestClient = new TestClient(
            "@becca:localhost", "foobar", "bazquux",
        );
        await beccaTestClient.client.initCrypto();

        await aliceTestClient.start();
        aliceTestClient.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
        await beccaTestClient.start();

        const beccaRoom = new Room(ROOM_ID, beccaTestClient.client, "@becca:localhost", {});
        beccaTestClient.client.store.storeRoom(beccaRoom);
        await beccaTestClient.client.setRoomEncryption(ROOM_ID, { "algorithm": "m.megolm.v1.aes-sha2" });

        const event = new MatrixEvent({
            type: "m.room.message",
            sender: "@becca:localhost",
            room_id: ROOM_ID,
            event_id: "$1",
            content: {
                msgtype: "m.text",
                body: "test message",
            },
        });

        await beccaTestClient.client.crypto.encryptEvent(event, beccaRoom);
        // remove keys from the event
        // @ts-ignore private properties
        event.clearEvent = undefined;
        // @ts-ignore private properties
        event.senderCurve25519Key = null;
        // @ts-ignore private properties
        event.claimedEd25519Key = null;

        const device = new DeviceInfo(beccaTestClient.client.deviceId);
        aliceTestClient.client.crypto.deviceList.getDeviceByIdentityKey = () => device;
        aliceTestClient.client.crypto.deviceList.getUserByIdentityKey = () => beccaTestClient.client.getUserId();

        // Create an olm session for Becca and Alice's devices
        const aliceOtks = await aliceTestClient.awaitOneTimeKeyUpload();
        const aliceOtkId = Object.keys(aliceOtks)[0];
        const aliceOtk = aliceOtks[aliceOtkId];
        const p2pSession = new global.Olm.Session();
        await beccaTestClient.client.crypto.cryptoStore.doTxn(
            'readonly',
            [IndexedDBCryptoStore.STORE_ACCOUNT],
            (txn) => {
                beccaTestClient.client.crypto.cryptoStore.getAccount(txn, (pickledAccount: string) => {
                    const account = new global.Olm.Account();
                    try {
                        account.unpickle(beccaTestClient.client.crypto.olmDevice.pickleKey, pickledAccount);
                        p2pSession.create_outbound(account, aliceTestClient.getDeviceKey(), aliceOtk.key);
                    } finally {
                        account.free();
                    }
                });
            },
        );

        const content = event.getWireContent();
        const groupSessionKey = await beccaTestClient.client.crypto.olmDevice.getInboundGroupSessionKey(
            ROOM_ID,
            content.sender_key,
            content.session_id,
        );
        const encryptedForwardedKey = encryptOlmEvent({
            sender: "@becca:localhost",
            senderKey: beccaTestClient.getDeviceKey(),
            recipient: aliceTestClient,
            p2pSession: p2pSession,
            plaincontent: {
                "algorithm": 'm.megolm.v1.aes-sha2',
                "room_id": ROOM_ID,
                "sender_key": content.sender_key,
                "sender_claimed_ed25519_key": groupSessionKey.sender_claimed_ed25519_key,
                "session_id": content.session_id,
                "session_key": groupSessionKey.key,
                "chain_index": groupSessionKey.chain_index,
                "forwarding_curve25519_key_chain": groupSessionKey.forwarding_curve25519_key_chain,
                "org.matrix.msc3061.shared_history": true,
            },
            plaintype: 'm.forwarded_room_key',
        });

        // Alice receives shared history
        aliceTestClient.httpBackend.when("GET", "/sync").respond(200, {
            next_batch: 1,
            to_device: { events: [encryptedForwardedKey] },
        });
        await aliceTestClient.flushSync();

        // Alice is invited to the room by Becca
        aliceTestClient.httpBackend.when("GET", "/sync").respond(200, {
            next_batch: 2,
            rooms: { invite: { [ROOM_ID]: { invite_state: { events: [
                {
                    sender: '@becca:localhost',
                    type: 'm.room.encryption',
                    state_key: '',
                    content: {
                        algorithm: 'm.megolm.v1.aes-sha2',
                    },
                },
                {
                    sender: '@becca:localhost',
                    type: 'm.room.member',
                    state_key: '@alice:localhost',
                    content: {
                        membership: 'invite',
                    },
                },
            ] } } } },
        });
        await aliceTestClient.flushSync();

        // Alice has joined the room
        aliceTestClient.httpBackend.when("GET", "/sync").respond(
            200, getSyncResponse(["@alice:localhost", "@becca:localhost"]),
        );
        await aliceTestClient.flushSync();

        aliceTestClient.httpBackend.when("GET", "/sync").respond(200, {
            next_batch: 4,
            rooms: {
                join: {
                    [ROOM_ID]: { timeline: { events: [event.event] } },
                },
            },
        });
        await aliceTestClient.flushSync();

        const room = aliceTestClient.client.getRoom(ROOM_ID);
        const roomEvent = room.getLiveTimeline().getEvents()[0];
        expect(roomEvent.isEncrypted()).toBe(true);
        const decryptedEvent = await testUtils.awaitDecryption(roomEvent);
        expect(decryptedEvent.getContent().body).toEqual('test message');

        await beccaTestClient.stop();
    });

    it("Alice receives shared history before being invited to a room by someone else", async () => {
        const beccaTestClient = new TestClient(
            "@becca:localhost", "foobar", "bazquux",
        );
        await beccaTestClient.client.initCrypto();

        await aliceTestClient.start();
        await beccaTestClient.start();

        const beccaRoom = new Room(ROOM_ID, beccaTestClient.client, "@becca:localhost", {});
        beccaTestClient.client.store.storeRoom(beccaRoom);
        await beccaTestClient.client.setRoomEncryption(ROOM_ID, { "algorithm": "m.megolm.v1.aes-sha2" });

        const event = new MatrixEvent({
            type: "m.room.message",
            sender: "@becca:localhost",
            room_id: ROOM_ID,
            event_id: "$1",
            content: {
                msgtype: "m.text",
                body: "test message",
            },
        });

        await beccaTestClient.client.crypto.encryptEvent(event, beccaRoom);
        // remove keys from the event
        // @ts-ignore private properties
        event.clearEvent = undefined;
        // @ts-ignore private properties
        event.senderCurve25519Key = null;
        // @ts-ignore private properties
        event.claimedEd25519Key = null;

        const device = new DeviceInfo(beccaTestClient.client.deviceId);
        aliceTestClient.client.crypto.deviceList.getDeviceByIdentityKey = () => device;

        // Create an olm session for Becca and Alice's devices
        const aliceOtks = await aliceTestClient.awaitOneTimeKeyUpload();
        const aliceOtkId = Object.keys(aliceOtks)[0];
        const aliceOtk = aliceOtks[aliceOtkId];
        const p2pSession = new global.Olm.Session();
        await beccaTestClient.client.crypto.cryptoStore.doTxn(
            'readonly',
            [IndexedDBCryptoStore.STORE_ACCOUNT],
            (txn) => {
                beccaTestClient.client.crypto.cryptoStore.getAccount(txn, (pickledAccount: string) => {
                    const account = new global.Olm.Account();
                    try {
                        account.unpickle(beccaTestClient.client.crypto.olmDevice.pickleKey, pickledAccount);
                        p2pSession.create_outbound(account, aliceTestClient.getDeviceKey(), aliceOtk.key);
                    } finally {
                        account.free();
                    }
                });
            },
        );

        const content = event.getWireContent();
        const groupSessionKey = await beccaTestClient.client.crypto.olmDevice.getInboundGroupSessionKey(
            ROOM_ID,
            content.sender_key,
            content.session_id,
        );
        const encryptedForwardedKey = encryptOlmEvent({
            sender: "@becca:localhost",
            senderKey: beccaTestClient.getDeviceKey(),
            recipient: aliceTestClient,
            p2pSession: p2pSession,
            plaincontent: {
                "algorithm": 'm.megolm.v1.aes-sha2',
                "room_id": ROOM_ID,
                "sender_key": content.sender_key,
                "sender_claimed_ed25519_key": groupSessionKey.sender_claimed_ed25519_key,
                "session_id": content.session_id,
                "session_key": groupSessionKey.key,
                "chain_index": groupSessionKey.chain_index,
                "forwarding_curve25519_key_chain": groupSessionKey.forwarding_curve25519_key_chain,
                "org.matrix.msc3061.shared_history": true,
            },
            plaintype: 'm.forwarded_room_key',
        });

        // Alice receives forwarded history from Becca
        aliceTestClient.httpBackend.when("GET", "/sync").respond(200, {
            next_batch: 1,
            to_device: { events: [encryptedForwardedKey] },
        });
        await aliceTestClient.flushSync();

        // Alice is invited to the room by Charlie
        aliceTestClient.httpBackend.when("GET", "/sync").respond(200, {
            next_batch: 2,
            rooms: { invite: { [ROOM_ID]: { invite_state: { events: [
                {
                    sender: '@becca:localhost',
                    type: 'm.room.encryption',
                    state_key: '',
                    content: {
                        algorithm: 'm.megolm.v1.aes-sha2',
                    },
                },
                {
                    sender: '@charlie:localhost',
                    type: 'm.room.member',
                    state_key: '@alice:localhost',
                    content: {
                        membership: 'invite',
                    },
                },
            ] } } } },
        });
        await aliceTestClient.flushSync();

        // Alice has joined the room
        aliceTestClient.httpBackend.when("GET", "/sync").respond(
            200, getSyncResponse(["@alice:localhost", "@becca:localhost", "@charlie:localhost"]),
        );
        await aliceTestClient.flushSync();

        aliceTestClient.httpBackend.when("GET", "/sync").respond(200, {
            next_batch: 4,
            rooms: {
                join: {
                    [ROOM_ID]: { timeline: { events: [event.event] } },
                },
            },
        });
        await aliceTestClient.flushSync();

        // Decryption should fail, because Alice hasn't received any keys she can trust
        const room = aliceTestClient.client.getRoom(ROOM_ID);
        const roomEvent = room.getLiveTimeline().getEvents()[0];
        expect(roomEvent.isEncrypted()).toBe(true);
        const decryptedEvent = await testUtils.awaitDecryption(roomEvent);
        expect(decryptedEvent.isDecryptionFailure()).toBe(true);

        await beccaTestClient.stop();
    });
    parseBeaconContent,

    describe("parseBeaconContent()", () => {
        it("should not explode when parsing an invalid beacon", () => {
            // deliberate cast to simulate wire content being invalid
            const result = parseBeaconContent({} as any);
            expect(result).toEqual({
                description: undefined,
                uri: undefined,
                timestamp: undefined,
            });
        });

        it("should parse unstable values", () => {
            const uri = "urigoeshere";
            const description = "descriptiongoeshere";
            const timestamp = 1234;
            const result = parseBeaconContent({
                "org.matrix.msc3488.location": {
                    uri,
                    description,
                },
                "org.matrix.msc3488.ts": timestamp,

                // relationship not used - just here to satisfy types
                "m.relates_to": {
                    rel_type: "m.reference",
                    event_id: "$unused",
                },
            });
            expect(result).toEqual({
                description,
                uri,
                timestamp,
            });
        });

        it("should parse stable values", () => {
            const uri = "urigoeshere";
            const description = "descriptiongoeshere";
            const timestamp = 1234;
            const result = parseBeaconContent({
                "m.location": {
                    uri,
                    description,
                },
                "m.ts": timestamp,

                // relationship not used - just here to satisfy types
                "m.relates_to": {
                    rel_type: "m.reference",
                    event_id: "$unused",
                },
            });
            expect(result).toEqual({
                description,
                uri,
                timestamp,
            });
        });
    });
import { RoomKeyRequestState } from '../../src/crypto/OutgoingRoomKeyRequestManager';
import { RoomMember } from '../../src/models/room-member';
            "algorithm": olmlib.MEGOLM_ALGORITHM,
            "room_id": roomId,
            "sender_key": eventContent.sender_key,
            "sender_claimed_ed25519_key": key.sender_claimed_ed25519_key,
            "session_id": eventContent.session_id,
            "session_key": key.key,
            "chain_index": key.chain_index,
            "forwarding_curve25519_key_chain": key.forwarding_curve_key_chain,
            "org.matrix.msc3061.shared_history": true,
    ksEvent.getWireType = () => "m.room.encrypted";
    ksEvent.getWireContent = () => {
        return {
            algorithm: "m.olm.v1.curve25519-aes-sha2",
        };
    };
    return ksEvent;
}

function roomKeyEventForEvent(client: MatrixClient, event: MatrixEvent): MatrixEvent {
    const roomId = event.getRoomId();
    const eventContent = event.getWireContent();
    const key = client.crypto.olmDevice.getOutboundGroupSessionKey(eventContent.session_id);
    const ksEvent = new MatrixEvent({
        type: "m.room_key",
        sender: client.getUserId(),
        content: {
            "algorithm": olmlib.MEGOLM_ALGORITHM,
            "room_id": roomId,
            "session_id": eventContent.session_id,
            "session_key": key.key,
        },
    });
    // make onRoomKeyEvent think this was an encrypted event
    // @ts-ignore private property
    ksEvent.senderCurve25519Key = event.getSenderKey();
    ksEvent.getWireType = () => "m.room.encrypted";
    ksEvent.getWireContent = () => {
        return {
            algorithm: "m.olm.v1.curve25519-aes-sha2",
        };
    };
            event.isKeySourceUntrusted = () => true;
        let claraClient: MatrixClient;
            claraClient = (new TestClient(
                "@clara:example.com", "claradevice",
            )).client;
            await claraClient.initCrypto();
            claraClient.stopClient();
        it("does not cancel keyshare requests until all messages are decrypted with trusted keys", async function() {
            // Make Bob invited by Alice so Bob will accept Alice's forwarded keys
            bobRoom.currentState.setStateEvents([new MatrixEvent({
                type: "m.room.member",
                sender: "@alice:example.com",
                room_id: roomId,
                content: { membership: "invite" },
                state_key: "@bob:example.com",
            })]);
            const device = new DeviceInfo(aliceClient.deviceId);
            bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;

            bobClient.crypto.deviceList.downloadKeys = () => Promise.resolve({});
            bobClient.crypto.deviceList.getUserByIdentityKey = () => "@alice:example.com";
            expect(events[0].isKeySourceUntrusted()).toBeTruthy();
            await sleep(1);
            // the room key request should still be there, since we've
            // decrypted everything with an untrusted key
            expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody)).toBeDefined();

            // Now share a trusted room key event so Bob will re-decrypt the messages.
            // Bob will backfill trust when they receive a trusted session with a higher
            // index that connects to an untrusted session with a lower index.
            const roomKeyEvent = roomKeyEventForEvent(aliceClient, events[1]);
            const trustedDecryptEventPromise = awaitEvent(events[0], "Event.decrypted");
            await bobDecryptor.onRoomKeyEvent(roomKeyEvent);
            await trustedDecryptEventPromise;
            expect(events[0].getContent().msgtype).not.toBe("m.bad.encrypted");
            expect(events[0].isKeySourceUntrusted()).toBeFalsy();
            // now the room key request should be gone, since there's
            // no better key to wait for
            const device = new DeviceInfo(aliceClient.deviceId);
            bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;


        it("should accept forwarded keys which it requested", async function() {
            const encryptionCfg = {
                "algorithm": "m.megolm.v1.aes-sha2",
            };
            const roomId = "!someroom";
            const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
            const bobRoom = new Room(roomId, bobClient, "@bob:example.com", {});
            aliceClient.store.storeRoom(aliceRoom);
            bobClient.store.storeRoom(bobRoom);
            await aliceClient.setRoomEncryption(roomId, encryptionCfg);
            await bobClient.setRoomEncryption(roomId, encryptionCfg);
            const events = [
                new MatrixEvent({
                    type: "m.room.message",
                    sender: "@alice:example.com",
                    room_id: roomId,
                    event_id: "$1",
                    content: {
                        msgtype: "m.text",
                        body: "1",
                    },
                }),
                new MatrixEvent({
                    type: "m.room.message",
                    sender: "@alice:example.com",
                    room_id: roomId,
                    event_id: "$2",
                    content: {
                        msgtype: "m.text",
                        body: "2",
                    },
                }),
            ];
            await Promise.all(events.map(async (event) => {
                // alice encrypts each event, and then bob tries to decrypt
                // them without any keys, so that they'll be in pending
                await aliceClient.crypto.encryptEvent(event, aliceRoom);
                // remove keys from the event
                // @ts-ignore private properties
                event.clearEvent = undefined;
                // @ts-ignore private properties
                event.senderCurve25519Key = null;
                // @ts-ignore private properties
                event.claimedEd25519Key = null;
                try {
                    await bobClient.crypto.decryptEvent(event);
                } catch (e) {
                    // we expect this to fail because we don't have the
                    // decryption keys yet
                }
            }));

            const device = new DeviceInfo(aliceClient.deviceId);
            bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
            bobClient.crypto.deviceList.getUserByIdentityKey = () => "@alice:example.com";

            const cryptoStore = bobClient.crypto.cryptoStore;
            const eventContent = events[0].getWireContent();
            const senderKey = eventContent.sender_key;
            const sessionId = eventContent.session_id;
            const roomKeyRequestBody = {
                algorithm: olmlib.MEGOLM_ALGORITHM,
                room_id: roomId,
                sender_key: senderKey,
                session_id: sessionId,
            };
            const outgoingReq = await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody);
            expect(outgoingReq).toBeDefined();
            await cryptoStore.updateOutgoingRoomKeyRequest(
                outgoingReq.requestId, RoomKeyRequestState.Unsent,
                { state: RoomKeyRequestState.Sent },
            );

            const bobDecryptor = bobClient.crypto.getRoomDecryptor(
                roomId, olmlib.MEGOLM_ALGORITHM,
            );

            const decryptEventsPromise = Promise.all(events.map((ev) => {
                return awaitEvent(ev, "Event.decrypted");
            }));
            const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
            await bobDecryptor.onRoomKeyEvent(ksEvent);
            const key = await bobClient.crypto.olmDevice.getInboundGroupSessionKey(
                roomId,
                events[0].getWireContent().sender_key,
                events[0].getWireContent().session_id,
            );
            expect(key).not.toBeNull();
            await decryptEventsPromise;
            expect(events[0].getContent().msgtype).not.toBe("m.bad.encrypted");
            expect(events[1].getContent().msgtype).not.toBe("m.bad.encrypted");
        });

        it("should accept forwarded keys from the user who invited it to the room", async function() {
            const encryptionCfg = {
                "algorithm": "m.megolm.v1.aes-sha2",
            };
            const roomId = "!someroom";
            const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
            const bobRoom = new Room(roomId, bobClient, "@bob:example.com", {});
            const claraRoom = new Room(roomId, claraClient, "@clara:example.com", {});
            // Make Bob invited by Clara
            bobRoom.currentState.setStateEvents([new MatrixEvent({
                type: "m.room.member",
                sender: "@clara:example.com",
                room_id: roomId,
                content: { membership: "invite" },
                state_key: "@bob:example.com",
            })]);
            aliceClient.store.storeRoom(aliceRoom);
            bobClient.store.storeRoom(bobRoom);
            claraClient.store.storeRoom(claraRoom);
            await aliceClient.setRoomEncryption(roomId, encryptionCfg);
            await bobClient.setRoomEncryption(roomId, encryptionCfg);
            await claraClient.setRoomEncryption(roomId, encryptionCfg);
            const events = [
                new MatrixEvent({
                    type: "m.room.message",
                    sender: "@alice:example.com",
                    room_id: roomId,
                    event_id: "$1",
                    content: {
                        msgtype: "m.text",
                        body: "1",
                    },
                }),
                new MatrixEvent({
                    type: "m.room.message",
                    sender: "@alice:example.com",
                    room_id: roomId,
                    event_id: "$2",
                    content: {
                        msgtype: "m.text",
                        body: "2",
                    },
                }),
            ];
            await Promise.all(events.map(async (event) => {
                // alice encrypts each event, and then bob tries to decrypt
                // them without any keys, so that they'll be in pending
                await aliceClient.crypto.encryptEvent(event, aliceRoom);
                // remove keys from the event
                // @ts-ignore private properties
                event.clearEvent = undefined;
                // @ts-ignore private properties
                event.senderCurve25519Key = null;
                // @ts-ignore private properties
                event.claimedEd25519Key = null;
                try {
                    await bobClient.crypto.decryptEvent(event);
                } catch (e) {
                    // we expect this to fail because we don't have the
                    // decryption keys yet
                }
            }));

            const device = new DeviceInfo(claraClient.deviceId);
            bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
            bobClient.crypto.deviceList.getUserByIdentityKey = () => "@clara:example.com";

            const bobDecryptor = bobClient.crypto.getRoomDecryptor(
                roomId, olmlib.MEGOLM_ALGORITHM,
            );

            const decryptEventsPromise = Promise.all(events.map((ev) => {
                return awaitEvent(ev, "Event.decrypted");
            }));
            const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
            ksEvent.event.sender = claraClient.getUserId(),
            ksEvent.sender = new RoomMember(roomId, claraClient.getUserId());
            await bobDecryptor.onRoomKeyEvent(ksEvent);
            const key = await bobClient.crypto.olmDevice.getInboundGroupSessionKey(
                roomId,
                events[0].getWireContent().sender_key,
                events[0].getWireContent().session_id,
            );
            expect(key).not.toBeNull();
            await decryptEventsPromise;
            expect(events[0].getContent().msgtype).not.toBe("m.bad.encrypted");
            expect(events[1].getContent().msgtype).not.toBe("m.bad.encrypted");
        });

        it("should accept forwarded keys from one of its own user's other devices", async function() {
            const encryptionCfg = {
                "algorithm": "m.megolm.v1.aes-sha2",
            };
            const roomId = "!someroom";
            const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
            const bobRoom = new Room(roomId, bobClient, "@bob:example.com", {});
            aliceClient.store.storeRoom(aliceRoom);
            bobClient.store.storeRoom(bobRoom);
            await aliceClient.setRoomEncryption(roomId, encryptionCfg);
            await bobClient.setRoomEncryption(roomId, encryptionCfg);
            const events = [
                new MatrixEvent({
                    type: "m.room.message",
                    sender: "@alice:example.com",
                    room_id: roomId,
                    event_id: "$1",
                    content: {
                        msgtype: "m.text",
                        body: "1",
                    },
                }),
                new MatrixEvent({
                    type: "m.room.message",
                    sender: "@alice:example.com",
                    room_id: roomId,
                    event_id: "$2",
                    content: {
                        msgtype: "m.text",
                        body: "2",
                    },
                }),
            ];
            await Promise.all(events.map(async (event) => {
                // alice encrypts each event, and then bob tries to decrypt
                // them without any keys, so that they'll be in pending
                await aliceClient.crypto.encryptEvent(event, aliceRoom);
                // remove keys from the event
                // @ts-ignore private properties
                event.clearEvent = undefined;
                // @ts-ignore private properties
                event.senderCurve25519Key = null;
                // @ts-ignore private properties
                event.claimedEd25519Key = null;
                try {
                    await bobClient.crypto.decryptEvent(event);
                } catch (e) {
                    // we expect this to fail because we don't have the
                    // decryption keys yet
                }
            }));

            const device = new DeviceInfo(claraClient.deviceId);
            device.verified = DeviceInfo.DeviceVerification.VERIFIED;
            bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
            bobClient.crypto.deviceList.getUserByIdentityKey = () => "@bob:example.com";

            const bobDecryptor = bobClient.crypto.getRoomDecryptor(
                roomId, olmlib.MEGOLM_ALGORITHM,
            );

            const decryptEventsPromise = Promise.all(events.map((ev) => {
                return awaitEvent(ev, "Event.decrypted");
            }));
            const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
            ksEvent.event.sender = bobClient.getUserId(),
            ksEvent.sender = new RoomMember(roomId, bobClient.getUserId());
            await bobDecryptor.onRoomKeyEvent(ksEvent);
            const key = await bobClient.crypto.olmDevice.getInboundGroupSessionKey(
                roomId,
                events[0].getWireContent().sender_key,
                events[0].getWireContent().session_id,
            );
            expect(key).not.toBeNull();
            await decryptEventsPromise;
            expect(events[0].getContent().msgtype).not.toBe("m.bad.encrypted");
            expect(events[1].getContent().msgtype).not.toBe("m.bad.encrypted");
        });

        it("should not accept unexpected forwarded keys for a room it's in", async function() {
            const encryptionCfg = {
                "algorithm": "m.megolm.v1.aes-sha2",
            };
            const roomId = "!someroom";
            const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
            const bobRoom = new Room(roomId, bobClient, "@bob:example.com", {});
            const claraRoom = new Room(roomId, claraClient, "@clara:example.com", {});
            aliceClient.store.storeRoom(aliceRoom);
            bobClient.store.storeRoom(bobRoom);
            claraClient.store.storeRoom(claraRoom);
            await aliceClient.setRoomEncryption(roomId, encryptionCfg);
            await bobClient.setRoomEncryption(roomId, encryptionCfg);
            await claraClient.setRoomEncryption(roomId, encryptionCfg);
            const events = [
                new MatrixEvent({
                    type: "m.room.message",
                    sender: "@alice:example.com",
                    room_id: roomId,
                    event_id: "$1",
                    content: {
                        msgtype: "m.text",
                        body: "1",
                    },
                }),
                new MatrixEvent({
                    type: "m.room.message",
                    sender: "@alice:example.com",
                    room_id: roomId,
                    event_id: "$2",
                    content: {
                        msgtype: "m.text",
                        body: "2",
                    },
                }),
            ];
            await Promise.all(events.map(async (event) => {
                // alice encrypts each event, and then bob tries to decrypt
                // them without any keys, so that they'll be in pending
                await aliceClient.crypto.encryptEvent(event, aliceRoom);
                // remove keys from the event
                // @ts-ignore private properties
                event.clearEvent = undefined;
                // @ts-ignore private properties
                event.senderCurve25519Key = null;
                // @ts-ignore private properties
                event.claimedEd25519Key = null;
                try {
                    await bobClient.crypto.decryptEvent(event);
                } catch (e) {
                    // we expect this to fail because we don't have the
                    // decryption keys yet
                }
            }));

            const device = new DeviceInfo(claraClient.deviceId);
            bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
            bobClient.crypto.deviceList.getUserByIdentityKey = () => "@alice:example.com";

            const bobDecryptor = bobClient.crypto.getRoomDecryptor(
                roomId, olmlib.MEGOLM_ALGORITHM,
            );

            const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
            ksEvent.event.sender = claraClient.getUserId(),
            ksEvent.sender = new RoomMember(roomId, claraClient.getUserId());
            await bobDecryptor.onRoomKeyEvent(ksEvent);
            const key = await bobClient.crypto.olmDevice.getInboundGroupSessionKey(
                roomId,
                events[0].getWireContent().sender_key,
                events[0].getWireContent().session_id,
            );
            expect(key).toBeNull();
        });

        it("should park forwarded keys for a room it's not in", async function() {
            const encryptionCfg = {
                "algorithm": "m.megolm.v1.aes-sha2",
            };
            const roomId = "!someroom";
            const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
            aliceClient.store.storeRoom(aliceRoom);
            await aliceClient.setRoomEncryption(roomId, encryptionCfg);
            const events = [
                new MatrixEvent({
                    type: "m.room.message",
                    sender: "@alice:example.com",
                    room_id: roomId,
                    event_id: "$1",
                    content: {
                        msgtype: "m.text",
                        body: "1",
                    },
                }),
                new MatrixEvent({
                    type: "m.room.message",
                    sender: "@alice:example.com",
                    room_id: roomId,
                    event_id: "$2",
                    content: {
                        msgtype: "m.text",
                        body: "2",
                    },
                }),
            ];
            await Promise.all(events.map(async (event) => {
                // alice encrypts each event, and then bob tries to decrypt
                // them without any keys, so that they'll be in pending
                await aliceClient.crypto.encryptEvent(event, aliceRoom);
                // remove keys from the event
                // @ts-ignore private properties
                event.clearEvent = undefined;
                // @ts-ignore private properties
                event.senderCurve25519Key = null;
                // @ts-ignore private properties
                event.claimedEd25519Key = null;
            }));

            const device = new DeviceInfo(aliceClient.deviceId);
            bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
            bobClient.crypto.deviceList.getUserByIdentityKey = () => "@alice:example.com";

            const bobDecryptor = bobClient.crypto.getRoomDecryptor(
                roomId, olmlib.MEGOLM_ALGORITHM,
            );

            const content = events[0].getWireContent();

            const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
            await bobDecryptor.onRoomKeyEvent(ksEvent);
            const bobKey = await bobClient.crypto.olmDevice.getInboundGroupSessionKey(
                roomId,
                content.sender_key,
                content.session_id,
            );
            expect(bobKey).toBeNull();

            const aliceKey = await aliceClient.crypto.olmDevice.getInboundGroupSessionKey(
                roomId,
                content.sender_key,
                content.session_id,
            );
            const parked = await bobClient.crypto.cryptoStore.takeParkedSharedHistory(roomId);
            expect(parked).toEqual([{
                senderId: aliceClient.getUserId(),
                senderKey: content.sender_key,
                sessionId: content.session_id,
                sessionKey: aliceKey.key,
                keysClaimed: { ed25519: aliceKey.sender_claimed_ed25519_key },
                forwardingCurve25519KeyChain: ["akey"],
            }]);
        });
            event.getWireType = () => "m.room.encrypted";
            event.getWireContent = () => {
                return {
                    algorithm: "m.olm.v1.curve25519-aes-sha2",
                };
            };
            event.getWireType = () => "m.room.encrypted";
            event.getWireContent = () => {
                return {
                    algorithm: "m.olm.v1.curve25519-aes-sha2",
                };
            };
import { DeviceInfo } from '../../../src/crypto/deviceinfo';
                verified: DeviceInfo.DeviceVerification.VERIFIED,
        osborne2.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
        osborne2.client.crypto.deviceList.getUserByIdentityKey = () => "@alice:example.com";

        await request.promise; // return value not used
            alice.client.crypto.setDeviceVerification = jest.fn();
            bob.client.crypto.setDeviceVerification = jest.fn();
            expect(alice.client.crypto.setDeviceVerification)
                .toHaveBeenCalledWith(
                    bob.client.getUserId(),
                    bob.client.deviceId,
                    true,
                    null,
                    null,
                    { "ed25519:Dynabook": "bob+base64+ed25519+key" },
            );
            expect(bob.client.crypto.setDeviceVerification)
                .toHaveBeenCalledWith(
                    alice.client.getUserId(),
                    alice.client.deviceId,
                    true,
                    null,
                    null,
                    { "ed25519:Osborne2": "alice+base64+ed25519+key" },
            );
import { REFERENCE_RELATION } from "matrix-events-sdk";
            it("should ignore invalid beacon events", () => {
                const beacon = new Beacon(makeBeaconInfoEvent(userId, roomId, { isLive: true, timeout: 60000 }));
                const emitSpy = jest.spyOn(beacon, 'emit');

                const ev = new MatrixEvent({
                    type: M_BEACON_INFO.name,
                    sender: userId,
                    room_id: roomId,
                    content: {
                        "m.relates_to": {
                            rel_type: REFERENCE_RELATION.name,
                            event_id: beacon.beaconInfoId,
                        },
                    },
                });
                beacon.addLocations([ev]);

                expect(beacon.latestLocationEvent).toBeFalsy();
                expect(emitSpy).not.toHaveBeenCalled();
            });

/*
Copyright 2022 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

export type OlmGroupSessionExtraData = {
    untrusted?: boolean;
    sharedHistory?: boolean;
};
Copyright 2021 - 2022 The Matrix.org Foundation C.I.C.
import { Optional } from "matrix-events-sdk/lib/types";

    public findIn<T>(obj: any): Optional<T> {
     * @param {boolean} options.toDevice the event is a to_device event
    uri?: string; // override from MLocationContent to allow optionals
    timestamp?: number;
    const location = M_LOCATION.findIn<MLocationContent>(content);
        description: location?.description,
        uri: location?.uri,
import { OlmGroupSessionExtraData } from "../@types/crypto";
    untrusted: boolean;
        extraSessionData: OlmGroupSessionExtraData = {},
                                if (existingSession.first_known_index() <= session.first_known_index()) {
                                    if (!existingSessionData.untrusted || extraSessionData.untrusted) {
                                        // existing session has less-than-or-equal index
                                        // (i.e. can decrypt at least as much), and the
                                        // new session's trust does not win over the old
                                        // session's trust, so keep it
                                        logger.log(`Keeping existing megolm session ${sessionId}`);
                                        return;
                                    }
                                    if (existingSession.first_known_index() < session.first_known_index()) {
                                        // We want to upgrade the existing session's trust,
                                        // but we can't just use the new session because we'll
                                        // lose the lower index. Check that the sessions connect
                                        // properly, and then manually set the existing session
                                        // as trusted.
                                        if (
                                            existingSession.export_session(session.first_known_index())
                                            === session.export_session(session.first_known_index())
                                        ) {
                                            logger.info(
                                                "Upgrading trust of existing megolm session " +
                                                sessionId + " based on newly-received trusted session",
                                            );
                                            existingSessionData.untrusted = false;
                                            this.cryptoStore.storeEndToEndInboundGroupSession(
                                                senderKey, sessionId, existingSessionData, txn,
                                            );
                                        } else {
                                            logger.warn(
                                                "Newly-received megolm session " + sessionId +
                                                " does not match existing session! Keeping existing session",
                                            );
                                        }
                                        return;
                                    }
                                    // If the sessions have the same index, go ahead and store the new trusted one.
                        const forwardingKeyChain = sessionData.forwardingCurve25519KeyChain || [];
                        // older forwarded keys didn't set the "untrusted"
                        // property, but can be identified by having a
                        // non-empty forwarding key chain.  These keys should
                        // be marked as untrusted since we don't know that they
                        // can be trusted
                        const untrusted = "untrusted" in sessionData
                            ? sessionData.untrusted
                            : forwardingKeyChain.length > 0;

                            "forwarding_curve25519_key_chain": forwardingKeyChain,
                            "untrusted": untrusted,

        if (!olmlib.isOlmEncrypted(event)) {
            logger.error("secret event not properly encrypted");
            return;
        }


        const senderKeyUser = this.baseApis.crypto.deviceList.getUserByIdentityKey(
            olmlib.OLM_ALGORITHM,
            content.sender_key,
        );
        if (senderKeyUser !== event.getSender()) {
            logger.error("sending device does not belong to the user it claims to be from");
            return;
        }

            // unsure that the sender is trusted.  In theory, this check is
            // unnecessary since we only accept secret shares from devices that
            // we requested from, but it doesn't hurt.
            const deviceTrust = this.baseApis.crypto.checkDeviceInfoTrust(event.getSender(), deviceInfo);
            if (!deviceTrust.isVerified()) {
                logger.log("secret share from unverified device");
                return;
            }
import { MatrixEvent } from "../../models/event";
import { RoomKeyRequestState } from '../OutgoingRoomKeyRequestManager';
import { OlmGroupSessionExtraData } from "../../@types/crypto";
    // events which we couldn't decrypt due to unknown sessions /
    // indexes, or which we could only decrypt with untrusted keys:
    // map from senderKey|sessionId to Set of MatrixEvents
        // Success. We can remove the event from the pending list, if
        // that hasn't already happened. However, if the event was
        // decrypted with an untrusted key, leave it on the pending
        // list so it will be retried if we find a trusted key later.
        if (!res.untrusted) {
            this.removeEventFromPendingList(event);
        }
        const extraSessionData: OlmGroupSessionExtraData = {};

        if (!olmlib.isOlmEncrypted(event)) {
            logger.error("key event not properly encrypted");
        if (content["org.matrix.msc3061.shared_history"]) {
            extraSessionData.sharedHistory = true;
        }

            const deviceInfo = this.crypto.deviceList.getDeviceByIdentityKey(
                olmlib.OLM_ALGORITHM,
                senderKey,
            );
            const senderKeyUser = this.baseApis.crypto.deviceList.getUserByIdentityKey(
                olmlib.OLM_ALGORITHM,
                senderKey,
            );
            if (senderKeyUser !== event.getSender()) {
                logger.error("sending device does not belong to the user it claims to be from");
                return;
            }
            const outgoingRequests = deviceInfo ? await this.crypto.cryptoStore.getOutgoingRoomKeyRequestsByTarget(
                event.getSender(), deviceInfo.deviceId, [RoomKeyRequestState.Sent],
            ) : [];
            const weRequested = outgoingRequests.some((req) => (
                req.requestBody.room_id === content.room_id && req.requestBody.session_id === content.session_id
            ));
            const room = this.baseApis.getRoom(content.room_id);
            const memberEvent = room?.getMember(this.userId)?.events.member;
            const fromInviter = memberEvent?.getSender() === event.getSender() ||
                (memberEvent?.getUnsigned()?.prev_sender === event.getSender() &&
                    memberEvent?.getPrevContent()?.membership === "invite");
            const fromUs = event.getSender() === this.baseApis.getUserId();

            if (!weRequested) {
                // If someone sends us an unsolicited key and it's not
                // shared history, ignore it
                if (!extraSessionData.sharedHistory) {
                    logger.log("forwarded key not shared history - ignoring");
                    return;
                }

                // If someone sends us an unsolicited key for a room
                // we're already in, and they're not one of our other
                // devices or the one who invited us, ignore it
                if (room && !fromInviter && !fromUs) {
                    logger.log("forwarded key not from inviter or from us - ignoring");
                    return;
                }
            }


            // If this is a key for a room we're not in, don't load it
            // yet, just park it in case *this sender* invites us to
            // that room later
            if (!room) {
                const parkedData = {
                    senderId: event.getSender(),
                    senderKey: content.sender_key,
                    sessionId: content.session_id,
                    sessionKey: content.session_key,
                    keysClaimed,
                    forwardingCurve25519KeyChain: forwardingKeyChain,
                };
                await this.crypto.cryptoStore.doTxn(
                    'readwrite',
                    ['parked_shared_history'],
                    (txn) => this.crypto.cryptoStore.addParkedSharedHistory(content.room_id, parkedData, txn),
                    logger.withPrefix("[addParkedSharedHistory]"),
                );
                return;
            }

            const sendingDevice = this.crypto.deviceList.getDeviceByIdentityKey(olmlib.OLM_ALGORITHM, senderKey);
            const deviceTrust = this.crypto.checkDeviceInfoTrust(event.getSender(), sendingDevice);

            if (fromUs && !deviceTrust.isVerified()) {
                return;
            }

            // forwarded keys are always untrusted
            extraSessionData.untrusted = true;

            // replace the sender key with the sender key of the session
            // creator for storage
            senderKey = content.sender_key;
            if (await this.retryDecryption(senderKey, content.session_id, !extraSessionData.untrusted)) {
        const extraSessionData: OlmGroupSessionExtraData = {};
            this.retryDecryption(session.sender_key, session.session_id, !extraSessionData.untrusted);
     * @param {Boolean} keyTrusted
     * @return {Boolean} whether all messages were successfully
     *     decrypted with trusted keys
    private async retryDecryption(senderKey: string, sessionId: string, keyTrusted?: boolean): Promise<boolean> {
                await ev.attemptDecryption(this.crypto, { isRetry: true, keyTrusted });
        // If decrypted successfully with trusted keys, they'll have
        // been removed from pendingEvents
        // check that the device that encrypted the event belongs to the user
        // that the event claims it's from.  We need to make sure that our
        // device list is up-to-date.  If the device is unknown, we can only
        // assume that the device logged out.  Some event handlers, such as
        // secret sharing, may be more strict and reject events that come from
        // unknown devices.
        await this.crypto.deviceList.downloadKeys([event.getSender()], false);
        const senderKeyUser = this.crypto.deviceList.getUserByIdentityKey(
            olmlib.OLM_ALGORITHM,
            deviceKey,
        );
        if (senderKeyUser !== event.getSender() && senderKeyUser !== undefined) {
            throw new DecryptionError(
                "OLM_BAD_SENDER",
                "Message claimed to be from " + event.getSender(), {
                    real_sender: senderKeyUser,
                },
            );
        }

     * @param {?Record<string, any>} keys The list of keys that was present
     * during the device verification. This will be double checked with the list
     * of keys the given device has currently.
     *
        keys?: Record<string, string>,
            const gotKeyId = keys ? Object.values(keys)[0] : null;
            if (keys && (Object.values(keys).length !== 1 || gotKeyId !== xsk.getId())) {
                throw new Error(`Key did not match expected value: expected ${xsk.getId()}, got ${gotKeyId}`);
            }
            if (keys) {
                for (const [keyId, key] of Object.entries(keys)) {
                    if (dev.keys[keyId] !== key) {
                        throw new Error(`Key did not match expected value: expected ${key}, got ${dev.keys[keyId]}`);
                    }
                }
            }
        if (event.isKeySourceUntrusted()) {
import { MatrixEvent } from "../models/event";
import { EventType } from "../@types/event";
/**
 * Check that an event was encrypted using olm.
 */
export function isOlmEncrypted(event: MatrixEvent): boolean {
    if (!event.getSenderKey()) {
        logger.error("Event has no sender key (not encrypted?)");
        return false;
    }
    if (event.getWireType() !== EventType.RoomMessageEncrypted ||
        !(["m.olm.v1.curve25519-aes-sha2"].includes(event.getWireContent().algorithm))) {
        logger.error("Event was not encrypted using an appropriate algorithm");
        return false;
    }
    return true;
}

import { MatrixEvent } from "../../models/event";
    addParkedSharedHistory(roomId: string, data: ParkedSharedHistory, txn?: unknown): void;
    takeParkedSharedHistory(roomId: string, txn?: unknown): Promise<ParkedSharedHistory[]>;

export interface ParkedSharedHistory {
    senderId: string;
    senderKey: string;
    sessionId: string;
    sessionKey: string;
    keysClaimed: ReturnType<MatrixEvent["getKeysClaimed"]>; // XXX: Less type dependence on MatrixEvent
    forwardingCurve25519KeyChain: string[];
}
    ParkedSharedHistory,
    public addParkedSharedHistory(
        roomId: string,
        parkedData: ParkedSharedHistory,
        txn?: IDBTransaction,
    ): void {
        if (!txn) {
            txn = this.db.transaction(
                "parked_shared_history", "readwrite",
            );
        }
        const objectStore = txn.objectStore("parked_shared_history");
        const req = objectStore.get([roomId]);
        req.onsuccess = () => {
            const { parked } = req.result || { parked: [] };
            parked.push(parkedData);
            objectStore.put({ roomId, parked });
        };
    }

    public takeParkedSharedHistory(
        roomId: string,
        txn?: IDBTransaction,
    ): Promise<ParkedSharedHistory[]> {
        if (!txn) {
            txn = this.db.transaction(
                "parked_shared_history", "readwrite",
            );
        }
        const cursorReq = txn.objectStore("parked_shared_history").openCursor(roomId);
        return new Promise((resolve, reject) => {
            cursorReq.onsuccess = () => {
                const cursor = cursorReq.result;
                if (!cursor) {
                    resolve([]);
                }
                const data = cursor.value;
                cursor.delete();
                resolve(data);
            };
            cursorReq.onerror = reject;
        });
    }

    if (oldVersion < 11) {
        db.createObjectStore("parked_shared_history", {
            keyPath: ["roomId"],
        });
    }
    ParkedSharedHistory,
    public static STORE_PARKED_SHARED_HISTORY = 'parked_shared_history';
    /**
     * Park a shared-history group session for a room we may be invited to later.
     */
    public addParkedSharedHistory(
        roomId: string,
        parkedData: ParkedSharedHistory,
        txn?: IDBTransaction,
    ): void {
        this.backend.addParkedSharedHistory(roomId, parkedData, txn);
    }

    /**
     * Pop out all shared-history group sessions for a room.
     */
    public takeParkedSharedHistory(
        roomId: string,
        txn?: IDBTransaction,
    ): Promise<ParkedSharedHistory[]> {
        return this.backend.takeParkedSharedHistory(roomId, txn);
    }

    ParkedSharedHistory,
    private parkedSharedHistory = new Map<string, ParkedSharedHistory[]>(); // keyed by room ID
    public addParkedSharedHistory(roomId: string, parkedData: ParkedSharedHistory): void {
        const parked = this.parkedSharedHistory.get(roomId) ?? [];
        parked.push(parkedData);
        this.parkedSharedHistory.set(roomId, parked);
    }

    public takeParkedSharedHistory(roomId: string): Promise<ParkedSharedHistory[]> {
        const parked = this.parkedSharedHistory.get(roomId) ?? [];
        this.parkedSharedHistory.delete(roomId);
        return Promise.resolve(parked);
    }

            new Promise<void>((resolve, reject) => {
                const crossSignId = this.baseApis.crypto.deviceList.getStoredCrossSigningForUser(this.userId)?.getId();
                if (crossSignId === this.deviceId) {
                    reject(new Error("Device ID is the same as the cross-signing ID"));
                }
                resolve();
            }).then(() => this.doVerification()).then(this.done.bind(this), this.cancel.bind(this));
        const verifiedDevices: [string, string, string][] = [];
                verifiedDevices.push([deviceId, keyId, device.keys[keyId]]);
                    verifiedDevices.push([deviceId, keyId, deviceId]);
        for (const [deviceId, keyId, key] of verifiedDevices) {
            await this.baseApis.crypto.setDeviceVerification(userId, deviceId, true, null, null, { [keyId]: key });
        }

        // if one of the user's own devices is being marked as verified / unverified,
        // check the key backup status, since whether or not we use this depends on
        // whether it has a signature from a verified device
        if (userId == this.baseApis.credentials.userId) {
            await this.baseApis.checkKeyBackup();
    toDevice?: boolean;
        if (options.toDevice) {
            delete plainOldJsObject.room_id;
        }

            const parsed = parseBeaconContent(content);
            if (!parsed.uri || !parsed.timestamp) return false; // we won't be able to process these
            const { timestamp } = parsed;
    keyTrusted?: boolean;
        if (this.clearEvent && !this.isDecryptionFailure() && !(this.isKeySourceUntrusted() && options.keyTrusted)) {
                .filter((eventJSON) => {
                    if (
                        eventJSON.type === EventType.RoomMessageEncrypted &&
                        !(["m.olm.v1.curve25519-aes-sha2"].includes(eventJSON.content?.algorithm))
                    ) {
                        logger.log(
                            'Ignoring invalid encrypted to-device event from ' + eventJSON.sender,
                        );
                        return false;
                    }

                    return true;
                })
                .map(client.getEventMapper({ toDevice: true }))

            const inviter = room.currentState.getStateEvents(EventType.RoomMember, client.getUserId())?.getSender();
            const parkedHistory = await client.crypto.cryptoStore.takeParkedSharedHistory(room.roomId);
            for (const parked of parkedHistory) {
                if (parked.senderId === inviter) {
                    await this.client.crypto.olmDevice.addInboundGroupSession(
                        room.roomId,
                        parked.senderKey,
                        parked.forwardingCurve25519KeyChain,
                        parked.sessionId,
                        parked.sessionKey,
                        parked.keysClaimed,
                        true,
                        { sharedHistory: true, untrusted: true },
                    );
                }
            }

            try {
                await this.processRoomEvents(room, stateEvents, events, syncEventData.fromCache);
            } catch (e) {
                logger.error(`Failed to process events on room ${room.roomId}:`, e);
            }