Skip to content

Commit 2fb1e65

Browse files
authored
Merge commit from fork
Remove insecure MatrixClient.sendSharedHistoryKeys method
2 parents 868bbfc + 1dcb7a6 commit 2fb1e65

File tree

3 files changed

+7
-110
lines changed

3 files changed

+7
-110
lines changed

spec/unit/models/MSC3089TreeSpace.spec.ts

Lines changed: 4 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ describe("MSC3089TreeSpace", () => {
107107
return Promise.resolve();
108108
});
109109
client.invite = fn;
110-
await tree.invite(target, false, false);
110+
await tree.invite(target, false);
111111
expect(fn).toHaveBeenCalledTimes(1);
112112
});
113113

@@ -120,7 +120,7 @@ describe("MSC3089TreeSpace", () => {
120120
return Promise.resolve();
121121
});
122122
client.invite = fn;
123-
await tree.invite(target, false, false);
123+
await tree.invite(target, false);
124124
expect(fn).toHaveBeenCalledTimes(2);
125125
});
126126

@@ -133,7 +133,7 @@ describe("MSC3089TreeSpace", () => {
133133
});
134134
client.invite = fn;
135135

136-
await expect(tree.invite(target, false, false)).rejects.toThrow("MatrixError: Sample Failure");
136+
await expect(tree.invite(target, false)).rejects.toThrow("MatrixError: Sample Failure");
137137

138138
expect(fn).toHaveBeenCalledTimes(1);
139139
});
@@ -155,61 +155,10 @@ describe("MSC3089TreeSpace", () => {
155155
{ invite: (userId) => fn(tree.roomId, userId) } as MSC3089TreeSpace,
156156
];
157157

158-
await tree.invite(target, true, false);
158+
await tree.invite(target, true);
159159
expect(fn).toHaveBeenCalledTimes(4);
160160
});
161161

162-
it("should share keys with invitees", async () => {
163-
const target = targetUser;
164-
const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => {
165-
expect(inviteRoomId).toEqual(roomId);
166-
expect(userIds).toMatchObject([target]);
167-
return Promise.resolve();
168-
});
169-
client.invite = () => Promise.resolve({}); // we're not testing this here - see other tests
170-
client.sendSharedHistoryKeys = sendKeysFn;
171-
172-
// Mock the history check as best as possible
173-
const historyVis = "shared";
174-
const historyFn = jest.fn().mockImplementation((eventType: string, stateKey?: string) => {
175-
// We're not expecting a super rigid test: the function that calls this internally isn't
176-
// really being tested here.
177-
expect(eventType).toEqual(EventType.RoomHistoryVisibility);
178-
expect(stateKey).toEqual("");
179-
return { getContent: () => ({ history_visibility: historyVis }) }; // eslint-disable-line camelcase
180-
});
181-
room.currentState.getStateEvents = historyFn;
182-
183-
// Note: inverse test is implicit from other tests, which disable the call stack of this
184-
// test in order to pass.
185-
await tree.invite(target, false, true);
186-
expect(sendKeysFn).toHaveBeenCalledTimes(1);
187-
expect(historyFn).toHaveBeenCalledTimes(1);
188-
});
189-
190-
it("should not share keys with invitees if inappropriate history visibility", async () => {
191-
const target = targetUser;
192-
const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => {
193-
expect(inviteRoomId).toEqual(roomId);
194-
expect(userIds).toMatchObject([target]);
195-
return Promise.resolve();
196-
});
197-
client.invite = () => Promise.resolve({}); // we're not testing this here - see other tests
198-
client.sendSharedHistoryKeys = sendKeysFn;
199-
200-
const historyVis = "joined"; // NOTE: Changed.
201-
const historyFn = jest.fn().mockImplementation((eventType: string, stateKey?: string) => {
202-
expect(eventType).toEqual(EventType.RoomHistoryVisibility);
203-
expect(stateKey).toEqual("");
204-
return { getContent: () => ({ history_visibility: historyVis }) }; // eslint-disable-line camelcase
205-
});
206-
room.currentState.getStateEvents = historyFn;
207-
208-
await tree.invite(target, false, true);
209-
expect(sendKeysFn).toHaveBeenCalledTimes(0);
210-
expect(historyFn).toHaveBeenCalledTimes(1);
211-
});
212-
213162
async function evaluatePowerLevels(pls: any, role: TreePermissions, expectedPl: number) {
214163
makePowerLevels(pls);
215164
const fn = jest

src/client.ts

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4087,43 +4087,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
40874087
await this.http.authedRequest(Method.Delete, path.path, path.queryData, undefined, { prefix: ClientPrefix.V3 });
40884088
}
40894089

4090-
/**
4091-
* Share shared-history decryption keys with the given users.
4092-
*
4093-
* @param roomId - the room for which keys should be shared.
4094-
* @param userIds - a list of users to share with. The keys will be sent to
4095-
* all of the user's current devices.
4096-
*
4097-
* @deprecated Do not use this method. It does not work with the Rust crypto stack, and even with the legacy
4098-
* stack it introduces a security vulnerability.
4099-
*/
4100-
public async sendSharedHistoryKeys(roomId: string, userIds: string[]): Promise<void> {
4101-
if (!this.crypto) {
4102-
throw new Error("End-to-end encryption disabled");
4103-
}
4104-
4105-
const roomEncryption = this.crypto?.getRoomEncryption(roomId);
4106-
if (!roomEncryption) {
4107-
// unknown room, or unencrypted room
4108-
this.logger.error("Unknown room. Not sharing decryption keys");
4109-
return;
4110-
}
4111-
4112-
const deviceInfos = await this.crypto.downloadKeys(userIds);
4113-
const devicesByUser: Map<string, DeviceInfo[]> = new Map();
4114-
for (const [userId, devices] of deviceInfos) {
4115-
devicesByUser.set(userId, Array.from(devices.values()));
4116-
}
4117-
4118-
// XXX: Private member access
4119-
const alg = this.crypto.getRoomDecryptor(roomId, roomEncryption.algorithm);
4120-
if (alg.sendSharedHistoryInboundSessions) {
4121-
await alg.sendSharedHistoryInboundSessions(devicesByUser);
4122-
} else {
4123-
this.logger.warn("Algorithm does not support sharing previous keys", roomEncryption.algorithm);
4124-
}
4125-
}
4126-
41274090
/**
41284091
* Get the config for the media repository.
41294092
* @returns Promise which resolves with an object containing the config.

src/models/MSC3089TreeSpace.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import {
3030
simpleRetryOperation,
3131
} from "../utils.ts";
3232
import { MSC3089Branch } from "./MSC3089Branch.ts";
33-
import { isRoomSharedHistory } from "../crypto/algorithms/megolm.ts";
3433
import { ISendEventResponse } from "../@types/requests.ts";
3534
import { FileType } from "../http-api/index.ts";
3635
import { KnownMembership } from "../@types/membership.ts";
@@ -136,28 +135,14 @@ export class MSC3089TreeSpace {
136135
* @param userId - The user ID to invite.
137136
* @param andSubspaces - True (default) to invite the user to all
138137
* directories/subspaces too, recursively.
139-
* @param shareHistoryKeys - True (default) to share encryption keys
140-
* with the invited user. This will allow them to decrypt the events (files)
141-
* in the tree. Keys will not be shared if the room is lacking appropriate
142-
* history visibility (by default, history visibility is "shared" in trees,
143-
* which is an appropriate visibility for these purposes).
144138
* @returns Promise which resolves when complete.
145139
*/
146-
public async invite(userId: string, andSubspaces = true, shareHistoryKeys = true): Promise<void> {
140+
public async invite(userId: string, andSubspaces = true): Promise<void> {
147141
const promises: Promise<void>[] = [this.retryInvite(userId)];
148142
if (andSubspaces) {
149-
promises.push(...this.getDirectories().map((d) => d.invite(userId, andSubspaces, shareHistoryKeys)));
143+
promises.push(...this.getDirectories().map((d) => d.invite(userId, andSubspaces)));
150144
}
151-
return Promise.all(promises).then(() => {
152-
// Note: key sharing is default on because for file trees it is relatively important that the invite
153-
// target can actually decrypt the files. The implied use case is that by inviting a user to the tree
154-
// it means the sender would like the receiver to view/download the files contained within, much like
155-
// sharing a folder in other circles.
156-
if (shareHistoryKeys && isRoomSharedHistory(this.room)) {
157-
// noinspection JSIgnoredPromiseFromCall - we aren't concerned as much if this fails.
158-
this.client.sendSharedHistoryKeys(this.roomId, [userId]);
159-
}
160-
});
145+
await Promise.all(promises);
161146
}
162147

163148
private retryInvite(userId: string): Promise<void> {

0 commit comments

Comments
 (0)