Skip to content

Commit

Permalink
Merge branch 'develop' into florianduros/rip-out-legacy-crypto/deprec…
Browse files Browse the repository at this point in the history
…ate-crypto-event-reexport
  • Loading branch information
florianduros authored Oct 15, 2024
2 parents 7489210 + 662b772 commit 60f3b2e
Show file tree
Hide file tree
Showing 17 changed files with 241 additions and 281 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
Changes in [34.8.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.8.0) (2024-10-15)
==================================================================================================
This release removes insecure functionality, resolving CVE-2024-47080 / GHSA-4jf8-g8wp-cx7c.

Changes in [34.7.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.7.0) (2024-10-08)
==================================================================================================
## 🦖 Deprecations

* RTCSession cleanup: deprecate getKeysForParticipant() and getEncryption(); add emitEncryptionKeys() ([#4427](https://github.com/matrix-org/matrix-js-sdk/pull/4427)). Contributed by @hughns.

## ✨ Features

* Bump matrix-rust-sdk to 9.1.0 ([#4435](https://github.com/matrix-org/matrix-js-sdk/pull/4435)). Contributed by @richvdh.
* Rotate Matrix RTC media encryption key when a new member joins a call for Post Compromise Security ([#4422](https://github.com/matrix-org/matrix-js-sdk/pull/4422)). Contributed by @hughns.
* Update media event content types to include captions ([#4403](https://github.com/matrix-org/matrix-js-sdk/pull/4403)). Contributed by @tulir.
* Update OIDC registration types to match latest MSC2966 state ([#4432](https://github.com/matrix-org/matrix-js-sdk/pull/4432)). Contributed by @t3chguy.
* Add `CryptoApi.pinCurrentUserIdentity` and `UserIdentity.needsUserApproval` ([#4415](https://github.com/matrix-org/matrix-js-sdk/pull/4415)). Contributed by @richvdh.


Changes in [34.6.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.6.0) (2024-09-24)
==================================================================================================
## 🦖 Deprecations
Expand Down
17 changes: 17 additions & 0 deletions babel.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,22 @@ module.exports = {
"@babel/plugin-transform-object-rest-spread",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-runtime",
[
"search-and-replace",
{
// Since rewriteImportExtensions doesn't work on dynamic imports (yet), we need to manually replace
// the dynamic rust-crypto import.
// (see https://github.com/babel/babel/issues/16750)
rules:
process.env.NODE_ENV !== "test"
? [
{
search: "./rust-crypto/index.ts",
replace: "./rust-crypto/index.js",
},
]
: [],
},
],
],
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "matrix-js-sdk",
"version": "34.6.0",
"version": "34.8.0",
"description": "Matrix Client-Server SDK for Javascript",
"engines": {
"node": ">=20.0.0"
Expand Down Expand Up @@ -91,6 +91,7 @@
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"babel-jest": "^29.0.0",
"babel-plugin-search-and-replace": "^1.1.1",
"debug": "^4.3.4",
"eslint": "8.57.0",
"eslint-config-google": "^0.14.0",
Expand Down
59 changes: 4 additions & 55 deletions spec/unit/models/MSC3089TreeSpace.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ describe("MSC3089TreeSpace", () => {
return Promise.resolve();
});
client.invite = fn;
await tree.invite(target, false, false);
await tree.invite(target, false);
expect(fn).toHaveBeenCalledTimes(1);
});

Expand All @@ -120,7 +120,7 @@ describe("MSC3089TreeSpace", () => {
return Promise.resolve();
});
client.invite = fn;
await tree.invite(target, false, false);
await tree.invite(target, false);
expect(fn).toHaveBeenCalledTimes(2);
});

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

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

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

await tree.invite(target, true, false);
await tree.invite(target, true);
expect(fn).toHaveBeenCalledTimes(4);
});

it("should share keys with invitees", async () => {
const target = targetUser;
const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => {
expect(inviteRoomId).toEqual(roomId);
expect(userIds).toMatchObject([target]);
return Promise.resolve();
});
client.invite = () => Promise.resolve({}); // we're not testing this here - see other tests
client.sendSharedHistoryKeys = sendKeysFn;

// Mock the history check as best as possible
const historyVis = "shared";
const historyFn = jest.fn().mockImplementation((eventType: string, stateKey?: string) => {
// We're not expecting a super rigid test: the function that calls this internally isn't
// really being tested here.
expect(eventType).toEqual(EventType.RoomHistoryVisibility);
expect(stateKey).toEqual("");
return { getContent: () => ({ history_visibility: historyVis }) }; // eslint-disable-line camelcase
});
room.currentState.getStateEvents = historyFn;

// Note: inverse test is implicit from other tests, which disable the call stack of this
// test in order to pass.
await tree.invite(target, false, true);
expect(sendKeysFn).toHaveBeenCalledTimes(1);
expect(historyFn).toHaveBeenCalledTimes(1);
});

it("should not share keys with invitees if inappropriate history visibility", async () => {
const target = targetUser;
const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => {
expect(inviteRoomId).toEqual(roomId);
expect(userIds).toMatchObject([target]);
return Promise.resolve();
});
client.invite = () => Promise.resolve({}); // we're not testing this here - see other tests
client.sendSharedHistoryKeys = sendKeysFn;

const historyVis = "joined"; // NOTE: Changed.
const historyFn = jest.fn().mockImplementation((eventType: string, stateKey?: string) => {
expect(eventType).toEqual(EventType.RoomHistoryVisibility);
expect(stateKey).toEqual("");
return { getContent: () => ({ history_visibility: historyVis }) }; // eslint-disable-line camelcase
});
room.currentState.getStateEvents = historyFn;

await tree.invite(target, false, true);
expect(sendKeysFn).toHaveBeenCalledTimes(0);
expect(historyFn).toHaveBeenCalledTimes(1);
});

async function evaluatePowerLevels(pls: any, role: TreePermissions, expectedPl: number) {
makePowerLevels(pls);
const fn = jest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { RustBackupCryptoEventMap, RustBackupCryptoEvents, RustBackupManager } f
import * as TestData from "../../test-utils/test-data";
import {
ConnectionError,
CryptoEvent,
HttpApiEvent,
HttpApiEventHandlerMap,
IHttpOpts,
Expand All @@ -37,6 +36,7 @@ import {
import * as testData from "../../test-utils/test-data";
import { BackupDecryptor } from "../../../src/common-crypto/CryptoBackend";
import { KeyBackupSession } from "../../../src/crypto-api/keybackup";
import { CryptoEvent } from "../../../src/crypto-api/index.ts";

describe("PerSessionKeyBackupDownloader", () => {
/** The downloader under test */
Expand Down
3 changes: 2 additions & 1 deletion spec/unit/rust-crypto/backup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { Mocked } from "jest-mock";
import fetchMock from "fetch-mock-jest";
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";

import { CryptoEvent, HttpApiEvent, HttpApiEventHandlerMap, MatrixHttpApi, TypedEventEmitter } from "../../../src";
import { HttpApiEvent, HttpApiEventHandlerMap, MatrixHttpApi, TypedEventEmitter } from "../../../src";
import { CryptoEvent } from "../../../src/crypto-api/index.ts";
import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
import * as testData from "../../test-utils/test-data";
import * as TestData from "../../test-utils/test-data";
Expand Down
2 changes: 1 addition & 1 deletion spec/unit/rust-crypto/rust-crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import fetchMock from "fetch-mock-jest";
import { RustCrypto } from "../../../src/rust-crypto/rust-crypto";
import { initRustCrypto } from "../../../src/rust-crypto";
import {
CryptoEvent,
Device,
DeviceVerification,
encodeBase64,
Expand Down Expand Up @@ -71,6 +70,7 @@ import { ClientEvent, ClientEventHandlerMap } from "../../../src/client";
import { Curve25519AuthData } from "../../../src/crypto-api/keybackup";
import encryptAESSecretStorageItem from "../../../src/utils/encryptAESSecretStorageItem.ts";
import { CryptoStore, SecretStorePrivateKeys } from "../../../src/crypto/store/base";
import { CryptoEvent } from "../../../src/crypto-api/index.ts";

const TEST_USER = "@alice:example.com";
const TEST_DEVICE_ID = "TEST_DEVICE";
Expand Down
107 changes: 37 additions & 70 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ import {
} from "./http-api/index.ts";
import {
Crypto,
CryptoEvent,
CryptoEventHandlerMap,
CryptoEvent as LegacyCryptoEvent,
CryptoEventHandlerMap as LegacyCryptoEventHandlerMap,
fixBackupKey,
ICheckOwnCrossSigningTrustOpts,
ICryptoCallbacks,
Expand Down Expand Up @@ -227,6 +227,8 @@ import {
CryptoApi,
decodeRecoveryKey,
ImportRoomKeysOpts,
CryptoEvent,
CryptoEventHandlerMap,
} from "./crypto-api/index.ts";
import { DeviceInfoMap } from "./crypto/DeviceList.ts";
import {
Expand Down Expand Up @@ -939,23 +941,25 @@ type RoomStateEvents =
| RoomStateEvent.Update
| RoomStateEvent.Marker;

type CryptoEvents =
| CryptoEvent.KeySignatureUploadFailure
| CryptoEvent.KeyBackupStatus
| CryptoEvent.KeyBackupFailed
| CryptoEvent.KeyBackupSessionsRemaining
| CryptoEvent.KeyBackupDecryptionKeyCached
| CryptoEvent.RoomKeyRequest
| CryptoEvent.RoomKeyRequestCancellation
| CryptoEvent.VerificationRequest
| CryptoEvent.VerificationRequestReceived
| CryptoEvent.DeviceVerificationChanged
| CryptoEvent.UserTrustStatusChanged
| CryptoEvent.KeysChanged
| CryptoEvent.Warning
| CryptoEvent.DevicesUpdated
| CryptoEvent.WillUpdateDevices
| CryptoEvent.LegacyCryptoStoreMigrationProgress;
type LegacyCryptoEvents =
| LegacyCryptoEvent.KeySignatureUploadFailure
| LegacyCryptoEvent.KeyBackupStatus
| LegacyCryptoEvent.KeyBackupFailed
| LegacyCryptoEvent.KeyBackupSessionsRemaining
| LegacyCryptoEvent.KeyBackupDecryptionKeyCached
| LegacyCryptoEvent.RoomKeyRequest
| LegacyCryptoEvent.RoomKeyRequestCancellation
| LegacyCryptoEvent.VerificationRequest
| LegacyCryptoEvent.VerificationRequestReceived
| LegacyCryptoEvent.DeviceVerificationChanged
| LegacyCryptoEvent.UserTrustStatusChanged
| LegacyCryptoEvent.KeysChanged
| LegacyCryptoEvent.Warning
| LegacyCryptoEvent.DevicesUpdated
| LegacyCryptoEvent.WillUpdateDevices
| LegacyCryptoEvent.LegacyCryptoStoreMigrationProgress;

type CryptoEvents = (typeof CryptoEvent)[keyof typeof CryptoEvent];

type MatrixEventEvents = MatrixEventEvent.Decrypted | MatrixEventEvent.Replaced | MatrixEventEvent.VisibilityChange;

Expand All @@ -976,6 +980,7 @@ export type EmittedEvents =
| ClientEvent
| RoomEvents
| RoomStateEvents
| LegacyCryptoEvents
| CryptoEvents
| MatrixEventEvents
| RoomMemberEvents
Expand Down Expand Up @@ -1187,6 +1192,7 @@ export type ClientEventHandlerMap = {
[ClientEvent.TurnServersError]: (error: Error, fatal: boolean) => void;
} & RoomEventHandlerMap &
RoomStateEventHandlerMap &
LegacyCryptoEventHandlerMap &
CryptoEventHandlerMap &
MatrixEventHandlerMap &
RoomMemberEventHandlerMap &
Expand Down Expand Up @@ -2176,16 +2182,16 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
const crypto = new Crypto(this, userId, this.deviceId, this.store, this.cryptoStore, this.verificationMethods!);

this.reEmitter.reEmit(crypto, [
CryptoEvent.KeyBackupFailed,
CryptoEvent.KeyBackupSessionsRemaining,
CryptoEvent.RoomKeyRequest,
CryptoEvent.RoomKeyRequestCancellation,
CryptoEvent.Warning,
CryptoEvent.DevicesUpdated,
CryptoEvent.WillUpdateDevices,
CryptoEvent.DeviceVerificationChanged,
CryptoEvent.UserTrustStatusChanged,
CryptoEvent.KeysChanged,
LegacyCryptoEvent.KeyBackupFailed,
LegacyCryptoEvent.KeyBackupSessionsRemaining,
LegacyCryptoEvent.RoomKeyRequest,
LegacyCryptoEvent.RoomKeyRequestCancellation,
LegacyCryptoEvent.Warning,
LegacyCryptoEvent.DevicesUpdated,
LegacyCryptoEvent.WillUpdateDevices,
LegacyCryptoEvent.DeviceVerificationChanged,
LegacyCryptoEvent.UserTrustStatusChanged,
LegacyCryptoEvent.KeysChanged,
]);

this.logger.debug("Crypto: initialising crypto object...");
Expand Down Expand Up @@ -2254,9 +2260,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// importing rust-crypto will download the webassembly, so we delay it until we know it will be
// needed.
this.logger.debug("Downloading Rust crypto library");
// blocked on https://github.com/matrix-org/matrix-js-sdk/issues/4392 / https://github.com/babel/babel/issues/16750
// eslint-disable-next-line node/file-extension-in-import
const RustCrypto = await import("./rust-crypto");
const RustCrypto = await import("./rust-crypto/index.ts");

const rustCrypto = await RustCrypto.initRustCrypto({
logger: this.logger,
Expand Down Expand Up @@ -2445,7 +2449,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @returns
*
* @remarks
* Fires {@link CryptoEvent.DeviceVerificationChanged}
* Fires {@link LegacyCryptoEvent.DeviceVerificationChanged}
*
* @deprecated Not supported for Rust Cryptography.
*/
Expand Down Expand Up @@ -4087,43 +4091,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
await this.http.authedRequest(Method.Delete, path.path, path.queryData, undefined, { prefix: ClientPrefix.V3 });
}

/**
* Share shared-history decryption keys with the given users.
*
* @param roomId - the room for which keys should be shared.
* @param userIds - a list of users to share with. The keys will be sent to
* all of the user's current devices.
*
* @deprecated Do not use this method. It does not work with the Rust crypto stack, and even with the legacy
* stack it introduces a security vulnerability.
*/
public async sendSharedHistoryKeys(roomId: string, userIds: string[]): Promise<void> {
if (!this.crypto) {
throw new Error("End-to-end encryption disabled");
}

const roomEncryption = this.crypto?.getRoomEncryption(roomId);
if (!roomEncryption) {
// unknown room, or unencrypted room
this.logger.error("Unknown room. Not sharing decryption keys");
return;
}

const deviceInfos = await this.crypto.downloadKeys(userIds);
const devicesByUser: Map<string, DeviceInfo[]> = new Map();
for (const [userId, devices] of deviceInfos) {
devicesByUser.set(userId, Array.from(devices.values()));
}

// XXX: Private member access
const alg = this.crypto.getRoomDecryptor(roomId, roomEncryption.algorithm);
if (alg.sendSharedHistoryInboundSessions) {
await alg.sendSharedHistoryInboundSessions(devicesByUser);
} else {
this.logger.warn("Algorithm does not support sharing previous keys", roomEncryption.algorithm);
}
}

/**
* Get the config for the media repository.
* @returns Promise which resolves with an object containing the config.
Expand Down
Loading

0 comments on commit 60f3b2e

Please sign in to comment.