Compare commits

...

14 Commits

Author SHA1 Message Date
RiotRobot b240c44128 v34.9.0-rc.0 2024-10-15 14:31:37 +00:00
RiotRobot 5508993d79 Merge branch 'master' into develop 2024-10-15 10:53:49 +00:00
RiotRobot c9b43ab251 v34.8.0 2024-10-15 10:53:23 +00:00
David Baker 2fb1e659c8 Merge commit from fork
Remove insecure MatrixClient.sendSharedHistoryKeys method
2024-10-15 11:48:16 +01:00
RiotRobot b842192a34 Merge branch 'master' into develop 2024-10-08 12:23:01 +00:00
Saul 860161bdd2 Fix the rust crypto import in esm environments. (#4445)
* Configure babel to fix the rust import in esm environments.

* Add lockfile changes.

* Cleanup rust-crypto import and babel config.
2024-10-08 10:34:01 +00:00
Florian Duros 0c9d82e40a Deprecate the crypto events which are not used by the rust-crypto (#4442)
* Deprecate crypto event not used by the rust-crypto

* Deprecate `Crypto.WillUpdateDevices` for `Crypto.DevicesUpdated`
2024-10-07 12:26:56 +00:00
Richard van der Hoff 73ab7c342a Expose crypto-api as its own typedoc module (#4439)
Currently the crypto-api hierarchy is exposed only as a `Crypto` namespace
under the "matrix" entrypoint in the documentation.

This isn't really right: it's meant to be a separate entrypoint (in the same
way as `types`, `testing` and `utils` are). This PR fixes that problem.
2024-10-07 11:38:28 +00:00
Hugh Nimmo-Smith 3386c66b98 Fix MatrixRTC sender key wrapping (#4441) 2024-10-07 10:34:23 +00:00
Florian Duros da044820d7 Clean AES export and move back calculateKeyCheck to secret-storage.ts (#4440) 2024-10-03 13:20:56 +00:00
renovate[bot] 9f40f32b3e Update dependency @types/node to v18.19.54 (#4436)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-01 16:37:25 +00:00
renovate[bot] f679942584 Update all non-major dependencies (#4438)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-01 16:16:22 +00:00
Richard van der Hoff 1dcb7a6e75 Remove insecure MatrixClient.sendSharedHistoryKeys method
This method is impossible to use securely, and so is being removed. (It also
didn't work under Rust cryptography.)

In future, this functionality may be re-introduced in a safer way, but doing so
will probably require updates to the MSC.
2024-09-26 12:03:45 +01:00
Richard van der Hoff 823316b2ff Remove use of insecure sendSharedHistoryKeys in MSC3089 impl 2024-09-26 12:01:51 +01:00
22 changed files with 176 additions and 221 deletions
+4
View File
@@ -1,3 +1,7 @@
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
+17
View File
@@ -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 -2
View File
@@ -1,6 +1,6 @@
{
"name": "matrix-js-sdk",
"version": "34.7.0",
"version": "34.9.0-rc.0",
"description": "Matrix Client-Server SDK for Javascript",
"engines": {
"node": ">=20.0.0"
@@ -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",
@@ -104,7 +105,7 @@
"eslint-plugin-tsdoc": "^0.3.0",
"eslint-plugin-unicorn": "^55.0.0",
"fake-indexeddb": "^5.0.2",
"fetch-mock": "11.1.3",
"fetch-mock": "11.1.5",
"fetch-mock-jest": "^1.5.1",
"husky": "^9.0.0",
"jest": "^29.0.0",
@@ -1010,6 +1010,48 @@ describe("MatrixRTCSession", () => {
}
});
it("Wraps key index around to 0 when it reaches the maximum", async () => {
// this should give us keys with index [0...255, 0, 1]
const membersToTest = 258;
const members: CallMembershipData[] = [];
for (let i = 0; i < membersToTest; i++) {
members.push(Object.assign({}, membershipTemplate, { device_id: `DEVICE${i}` }));
}
jest.useFakeTimers();
try {
// start with a single member
const mockRoom = makeMockRoom(members.slice(0, 1));
for (let i = 0; i < membersToTest; i++) {
const keysSentPromise = new Promise<EncryptionKeysEventContent>((resolve) => {
sendEventMock.mockImplementation((_roomId, _evType, payload) => resolve(payload));
});
if (i === 0) {
// if first time around then set up the session
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
} else {
// otherwise update the state
mockRoom.getLiveTimeline().getState = jest
.fn()
.mockReturnValue(makeMockRoomState(members.slice(0, i + 1), mockRoom.roomId));
}
sess!.onMembershipUpdate();
// advance time to avoid key throttling
jest.advanceTimersByTime(10000);
const keysPayload = await keysSentPromise;
expect(keysPayload.keys).toHaveLength(1);
expect(keysPayload.keys[0].index).toEqual(i % 256);
}
} finally {
jest.useRealTimers();
}
});
it("Doesn't re-send key immediately", async () => {
const realSetTimeout = setTimeout;
jest.useFakeTimers();
+4 -55
View File
@@ -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);
});
@@ -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);
});
@@ -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);
});
@@ -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
+1 -1
View File
@@ -18,6 +18,7 @@ import { Mocked } from "jest-mock";
import {
AccountDataClient,
calculateKeyCheck,
PassphraseInfo,
SecretStorageCallbacks,
SecretStorageKeyDescriptionAesV1,
@@ -26,7 +27,6 @@ import {
trimTrailingEquals,
} from "../../src/secret-storage";
import { randomString } from "../../src/randomstring";
import { calculateKeyCheck } from "../../src/calculateKeyCheck.ts";
describe("ServerSideSecretStorageImpl", function () {
describe(".addKey", function () {
-34
View File
@@ -1,34 +0,0 @@
/*
* Copyright 2024 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.
*/
// string of zeroes, for calculating the key check
import encryptAESSecretStorageItem from "./utils/encryptAESSecretStorageItem.ts";
import { AESEncryptedSecretStoragePayload } from "./@types/AESEncryptedSecretStoragePayload.ts";
const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
/**
* Calculate the MAC for checking the key.
* See https://spec.matrix.org/v1.11/client-server-api/#msecret_storagev1aes-hmac-sha2, steps 3 and 4.
*
* @param key - the key to use
* @param iv - The initialization vector as a base64-encoded string.
* If omitted, a random initialization vector will be created.
* @returns An object that contains, `mac` and `iv` properties.
*/
export function calculateKeyCheck(key: Uint8Array, iv?: string): Promise<AESEncryptedSecretStoragePayload> {
return encryptAESSecretStorageItem(ZERO_STR, key, "", iv);
}
+1 -40
View File
@@ -2254,9 +2254,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,
@@ -4087,43 +4085,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.
+21 -12
View File
@@ -25,6 +25,15 @@ import { BackupTrustInfo, KeyBackupCheck, KeyBackupInfo } from "./keybackup.ts";
import { ISignatures } from "../@types/signed.ts";
import { MatrixEvent } from "../models/event.ts";
/**
* `matrix-js-sdk/lib/crypto-api`: End-to-end encryption support.
*
* The most important type is {@link CryptoApi}, an instance of which can be retrieved via
* {@link MatrixClient.getCrypto}.
*
* @packageDocumentation
*/
/**
* Public interface to the cryptography parts of the js-sdk
*
@@ -181,7 +190,7 @@ export interface CryptoApi {
/**
* Return whether we trust other user's signatures of their devices.
*
* @see {@link Crypto.CryptoApi#setTrustCrossSignedDevices}
* @see {@link CryptoApi.setTrustCrossSignedDevices}
*
* @returns `true` if we trust cross-signed devices, otherwise `false`.
*/
@@ -228,7 +237,7 @@ export interface CryptoApi {
*
* @throws an error if the device is unknown, or has not published any encryption keys.
*
* @remarks Fires {@link CryptoEvent#DeviceVerificationChanged}
* @remarks Fires {@link matrix.CryptoEvent.DeviceVerificationChanged}
*/
setDeviceVerified(userId: string, deviceId: string, verified?: boolean): Promise<void>;
@@ -259,7 +268,7 @@ export interface CryptoApi {
*
* @returns True if cross-signing is ready to be used on this device
*
* @throws May throw {@link ClientStoppedError} if the `MatrixClient` is stopped before or during the call.
* @throws May throw {@link matrix.ClientStoppedError} if the `MatrixClient` is stopped before or during the call.
*/
isCrossSigningReady(): Promise<boolean>;
@@ -327,7 +336,7 @@ export interface CryptoApi {
* @returns The current status of cross-signing keys: whether we have public and private keys cached locally, and
* whether the private keys are in secret storage.
*
* @throws May throw {@link ClientStoppedError} if the `MatrixClient` is stopped before or during the call.
* @throws May throw {@link matrix.ClientStoppedError} if the `MatrixClient` is stopped before or during the call.
*/
getCrossSigningStatus(): Promise<CrossSigningStatus>;
@@ -407,8 +416,8 @@ export interface CryptoApi {
*
* If an all-devices verification is already in flight, returns it. Otherwise, initiates a new one.
*
* To control the methods offered, set {@link ICreateClientOpts.verificationMethods} when creating the
* MatrixClient.
* To control the methods offered, set {@link matrix.ICreateClientOpts.verificationMethods} when creating the
* `MatrixClient`.
*
* @returns a VerificationRequest when the request has been sent to the other party.
*/
@@ -422,8 +431,8 @@ export interface CryptoApi {
*
* If a verification for this user/device is already in flight, returns it. Otherwise, initiates a new one.
*
* To control the methods offered, set {@link ICreateClientOpts.verificationMethods} when creating the
* MatrixClient.
* To control the methods offered, set {@link matrix.ICreateClientOpts.verificationMethods} when creating the
* `MatrixClient`.
*
* @param userId - ID of the owner of the device to verify
* @param deviceId - ID of the device to verify
@@ -480,7 +489,7 @@ export interface CryptoApi {
/**
* Determine if a key backup can be trusted.
*
* @param info - key backup info dict from {@link MatrixClient#getKeyBackupVersion}.
* @param info - key backup info dict from {@link matrix.MatrixClient.getKeyBackupVersion}.
*/
isKeyBackupTrusted(info: KeyBackupInfo): Promise<BackupTrustInfo>;
@@ -500,7 +509,7 @@ export interface CryptoApi {
*
* If there are existing backups they will be replaced.
*
* The decryption key will be saved in Secret Storage (the {@link SecretStorageCallbacks.getSecretStorageKey} Crypto
* The decryption key will be saved in Secret Storage (the {@link matrix.SecretStorage.SecretStorageCallbacks.getSecretStorageKey} Crypto
* callback will be called)
* and the backup engine will be started.
*/
@@ -841,9 +850,9 @@ export class DeviceVerificationStatus {
* Check if we should consider this device "verified".
*
* A device is "verified" if either:
* * it has been manually marked as such via {@link MatrixClient#setDeviceVerified}.
* * it has been manually marked as such via {@link matrix.MatrixClient.setDeviceVerified}.
* * it has been cross-signed with a verified signing key, **and** the client has been configured to trust
* cross-signed devices via {@link Crypto.CryptoApi#setTrustCrossSignedDevices}.
* cross-signed devices via {@link CryptoApi.setTrustCrossSignedDevices}.
*
* @returns true if this device is verified via any means.
*/
+2 -2
View File
@@ -35,7 +35,7 @@ export interface Aes256AuthData {
* Information about a server-side key backup.
*
* Returned by [`GET /_matrix/client/v3/room_keys/version`](https://spec.matrix.org/v1.7/client-server-api/#get_matrixclientv3room_keysversion)
* and hence {@link MatrixClient#getKeyBackupVersion}.
* and hence {@link matrix.MatrixClient.getKeyBackupVersion}.
*/
export interface KeyBackupInfo {
algorithm: string;
@@ -63,7 +63,7 @@ export interface BackupTrustInfo {
}
/**
* The result of {@link Crypto.CryptoApi.checkKeyBackupAndEnable}.
* The result of {@link CryptoApi.checkKeyBackupAndEnable}.
*/
export interface KeyBackupCheck {
backupInfo: KeyBackupInfo;
+2 -2
View File
@@ -119,7 +119,7 @@ export interface VerificationRequest
*
* If a verifier has already been created for this request, returns that verifier.
*
* This does *not* send the `m.key.verification.start` event - to do so, call {@link Crypto.Verifier#verify} on the
* This does *not* send the `m.key.verification.start` event - to do so, call {@link Verifier.verify} on the
* returned verifier.
*
* If no previous events have been sent, pass in `targetDevice` to set who to direct this request to.
@@ -281,7 +281,7 @@ export interface Verifier extends TypedEventEmitter<VerifierEvent, VerifierEvent
* Cancel a verification.
*
* We will send an `m.key.verification.cancel` if the verification is still in flight. The verification promise
* will reject, and a {@link Crypto.VerifierEvent#Cancel} will be emitted.
* will reject, and a {@link crypto-api.VerifierEvent.Cancel | VerifierEvent.Cancel} will be emitted.
*
* @param e - the reason for the cancellation.
*/
+3 -6
View File
@@ -16,11 +16,8 @@ limitations under the License.
import encryptAESSecretStorageItem from "../utils/encryptAESSecretStorageItem.ts";
import decryptAESSecretStorageItem from "../utils/decryptAESSecretStorageItem.ts";
import { AESEncryptedSecretStoragePayload } from "../@types/AESEncryptedSecretStoragePayload.ts";
// Export for backwards compatibility
export type { AESEncryptedSecretStoragePayload as IEncryptedPayload };
// Export with new names instead of using `as` to not break react-sdk tests
export const encryptAES = encryptAESSecretStorageItem;
export const decryptAES = decryptAESSecretStorageItem;
export { calculateKeyCheck } from "../calculateKeyCheck.ts";
export type { AESEncryptedSecretStoragePayload as IEncryptedPayload } from "../@types/AESEncryptedSecretStoragePayload.ts";
export { encryptAESSecretStorageItem as encryptAES, decryptAESSecretStorageItem as decryptAES };
export { calculateKeyCheck } from "../secret-storage.ts";
+1 -1
View File
@@ -43,7 +43,7 @@ import { encodeRecoveryKey } from "../crypto-api/index.ts";
import decryptAESSecretStorageItem from "../utils/decryptAESSecretStorageItem.ts";
import encryptAESSecretStorageItem from "../utils/encryptAESSecretStorageItem.ts";
import { AESEncryptedSecretStoragePayload } from "../@types/AESEncryptedSecretStoragePayload.ts";
import { calculateKeyCheck } from "../calculateKeyCheck.ts";
import { calculateKeyCheck } from "../secret-storage.ts";
const KEY_BACKUP_KEYS_PER_REQUEST = 200;
const KEY_BACKUP_CHECK_RATE_LIMIT = 5000; // ms
+8 -1
View File
@@ -75,6 +75,7 @@ import { MapWithDefault, recursiveMapToObject } from "../utils.ts";
import {
AccountDataClient,
AddSecretStorageKeyOpts,
calculateKeyCheck,
SECRET_STORAGE_ALGORITHM_V1_AES,
SecretStorageKeyDescription,
SecretStorageKeyObject,
@@ -109,7 +110,6 @@ import { KnownMembership } from "../@types/membership.ts";
import decryptAESSecretStorageItem from "../utils/decryptAESSecretStorageItem.ts";
import encryptAESSecretStorageItem from "../utils/encryptAESSecretStorageItem.ts";
import { AESEncryptedSecretStoragePayload } from "../@types/AESEncryptedSecretStoragePayload.ts";
import { calculateKeyCheck } from "../calculateKeyCheck.ts";
/* re-exports for backwards compatibility */
export type {
@@ -230,10 +230,14 @@ export interface IMegolmEncryptedContent {
export type IEncryptedContent = IOlmEncryptedContent | IMegolmEncryptedContent;
export enum CryptoEvent {
/** @deprecated Event not fired by the rust crypto */
DeviceVerificationChanged = "deviceVerificationChanged",
UserTrustStatusChanged = "userTrustStatusChanged",
/** @deprecated Event not fired by the rust crypto */
UserCrossSigningUpdated = "userCrossSigningUpdated",
/** @deprecated Event not fired by the rust crypto */
RoomKeyRequest = "crypto.roomKeyRequest",
/** @deprecated Event not fired by the rust crypto */
RoomKeyRequestCancellation = "crypto.roomKeyRequestCancellation",
KeyBackupStatus = "crypto.keyBackupStatus",
KeyBackupFailed = "crypto.keyBackupFailed",
@@ -250,6 +254,7 @@ export enum CryptoEvent {
*/
KeyBackupDecryptionKeyCached = "crypto.keyBackupDecryptionKeyCached",
/** @deprecated Event not fired by the rust crypto */
KeySignatureUploadFailure = "crypto.keySignatureUploadFailure",
/** @deprecated Use `VerificationRequestReceived`. */
VerificationRequest = "crypto.verification.request",
@@ -261,7 +266,9 @@ export enum CryptoEvent {
*/
VerificationRequestReceived = "crypto.verificationRequestReceived",
/** @deprecated Event not fired by the rust crypto */
Warning = "crypto.warning",
/** @deprecated Use {@link DevicesUpdated} instead when using rust crypto */
WillUpdateDevices = "crypto.willUpdateDevices",
DevicesUpdated = "crypto.devicesUpdated",
KeysChanged = "crossSigning.keysChanged",
+1 -6
View File
@@ -108,12 +108,7 @@ export type { ISSOFlow as SSOFlow, LoginFlow } from "./@types/auth.ts";
export type { IHierarchyRelation as HierarchyRelation, IHierarchyRoom as HierarchyRoom } from "./@types/spaces.ts";
export { LocationAssetType } from "./@types/location.ts";
/**
* Types supporting cryptography.
*
* The most important is {@link Crypto.CryptoApi}, an instance of which can be retrieved via
* {@link MatrixClient.getCrypto}.
*/
/** @deprecated Backwards-compatibility re-export. Import from `crypto-api` directly. */
export * as Crypto from "./crypto-api/index.ts";
let cryptoStoreFactory = (): CryptoStore => new MemoryCryptoStore();
+5 -6
View File
@@ -448,13 +448,12 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
}
private getNewEncryptionKeyIndex(): number {
const userId = this.client.getUserId();
const deviceId = this.client.getDeviceId();
if (this.currentEncryptionKeyIndex === -1) {
return 0;
}
if (!userId) throw new Error("No userId!");
if (!deviceId) throw new Error("No deviceId!");
return (this.getKeysForParticipantInternal(userId, deviceId)?.length ?? 0) % 16;
// maximum key index is 255
return (this.currentEncryptionKeyIndex + 1) % 256;
}
/**
+3 -18
View File
@@ -30,7 +30,6 @@ import {
simpleRetryOperation,
} from "../utils.ts";
import { MSC3089Branch } from "./MSC3089Branch.ts";
import { isRoomSharedHistory } from "../crypto/algorithms/megolm.ts";
import { ISendEventResponse } from "../@types/requests.ts";
import { FileType } from "../http-api/index.ts";
import { KnownMembership } from "../@types/membership.ts";
@@ -136,28 +135,14 @@ export class MSC3089TreeSpace {
* @param userId - The user ID to invite.
* @param andSubspaces - True (default) to invite the user to all
* directories/subspaces too, recursively.
* @param shareHistoryKeys - True (default) to share encryption keys
* with the invited user. This will allow them to decrypt the events (files)
* in the tree. Keys will not be shared if the room is lacking appropriate
* history visibility (by default, history visibility is "shared" in trees,
* which is an appropriate visibility for these purposes).
* @returns Promise which resolves when complete.
*/
public async invite(userId: string, andSubspaces = true, shareHistoryKeys = true): Promise<void> {
public async invite(userId: string, andSubspaces = true): Promise<void> {
const promises: Promise<void>[] = [this.retryInvite(userId)];
if (andSubspaces) {
promises.push(...this.getDirectories().map((d) => d.invite(userId, andSubspaces, shareHistoryKeys)));
promises.push(...this.getDirectories().map((d) => d.invite(userId, andSubspaces)));
}
return Promise.all(promises).then(() => {
// Note: key sharing is default on because for file trees it is relatively important that the invite
// target can actually decrypt the files. The implied use case is that by inviting a user to the tree
// it means the sender would like the receiver to view/download the files contained within, much like
// sharing a folder in other circles.
if (shareHistoryKeys && isRoomSharedHistory(this.room)) {
// noinspection JSIgnoredPromiseFromCall - we aren't concerned as much if this fails.
this.client.sendSharedHistoryKeys(this.roomId, [userId]);
}
});
await Promise.all(promises);
}
private retryInvite(userId: string): Promise<void> {
+4 -4
View File
@@ -65,7 +65,7 @@ export class TypedEventEmitter<
SuperclassArguments extends ListenerMap<any> = Arguments,
> extends EventEmitter {
/**
* Alias for {@link TypedEventEmitter#on}.
* Alias for {@link on}.
*/
public addListener<T extends Events | EventEmitterEvents>(
event: T,
@@ -124,7 +124,7 @@ export class TypedEventEmitter<
}
/**
* Alias for {@link TypedEventEmitter#removeListener}
* Alias for {@link removeListener}
*/
public off<T extends Events | EventEmitterEvents>(event: T, listener: Listener<Events, Arguments, T>): this {
return super.off(event, listener);
@@ -139,7 +139,7 @@ export class TypedEventEmitter<
* being added, and called, multiple times.
*
* By default, event listeners are invoked in the order they are added. The
* {@link TypedEventEmitter#prependListener} method can be used as an alternative to add the
* {@link prependListener} method can be used as an alternative to add the
* event listener to the beginning of the listeners array.
*
* @param event - The name of the event.
@@ -158,7 +158,7 @@ export class TypedEventEmitter<
* Returns a reference to the `EventEmitter`, so that calls can be chained.
*
* By default, event listeners are invoked in the order they are added.
* The {@link TypedEventEmitter#prependOnceListener} method can be used as an alternative to add the
* The {@link prependOnceListener} method can be used as an alternative to add the
* event listener to the beginning of the listeners array.
*
* @param event - The name of the event.
+17 -2
View File
@@ -28,7 +28,6 @@ import { logger } from "./logger.ts";
import encryptAESSecretStorageItem from "./utils/encryptAESSecretStorageItem.ts";
import decryptAESSecretStorageItem from "./utils/decryptAESSecretStorageItem.ts";
import { AESEncryptedSecretStoragePayload } from "./@types/AESEncryptedSecretStoragePayload.ts";
import { calculateKeyCheck } from "./crypto/aes.ts";
export const SECRET_STORAGE_ALGORITHM_V1_AES = "m.secret_storage.v1.aes-hmac-sha2";
@@ -165,7 +164,7 @@ export interface SecretStorageCallbacks {
* Descriptions of the secret storage keys are also stored in server-side storage, per the
* [matrix specification](https://spec.matrix.org/v1.6/client-server-api/#key-storage), so
* before a key can be used in this way, it must have been stored on the server. This is
* done via {@link SecretStorage.ServerSideSecretStorage#addKey}.
* done via {@link ServerSideSecretStorage#addKey}.
*
* Obviously the keys themselves are not stored server-side, so the js-sdk calls this callback
* in order to retrieve a secret storage key from the application.
@@ -676,3 +675,19 @@ export function trimTrailingEquals(input: string): string {
return input;
}
}
// string of zeroes, for calculating the key check
const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
/**
* Calculate the MAC for checking the key.
* See https://spec.matrix.org/v1.11/client-server-api/#msecret_storagev1aes-hmac-sha2, steps 3 and 4.
*
* @param key - the key to use
* @param iv - The initialization vector as a base64-encoded string.
* If omitted, a random initialization vector will be created.
* @returns An object that contains, `mac` and `iv` properties.
*/
export function calculateKeyCheck(key: Uint8Array, iv?: string): Promise<AESEncryptedSecretStoragePayload> {
return encryptAESSecretStorageItem(ZERO_STR, key, "", iv);
}
+5 -2
View File
@@ -24,7 +24,7 @@ import { logger } from "../logger.ts";
import { ISavedSync } from "./index.ts";
import { IIndexedDBBackend } from "./indexeddb-backend.ts";
import { ISyncResponse } from "../sync-accumulator.ts";
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
import { EventEmitterEvents, TypedEventEmitter } from "../models/typed-event-emitter.ts";
import { IStateEventWithRoomId } from "../@types/search.ts";
import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage.ts";
import { IStoredClientOpts } from "../client.ts";
@@ -118,7 +118,10 @@ export class IndexedDBStore extends MemoryStore {
}
}
public on = this.emitter.on.bind(this.emitter);
/** Re-exports `TypedEventEmitter.on` */
public on(event: EventEmitterEvents | "degraded" | "closed", handler: (...args: any[]) => void): void {
this.emitter.on(event, handler);
}
/**
* @returns Resolved when loaded from indexed db.
+1 -1
View File
@@ -2,7 +2,7 @@
"$schema": "https://typedoc.org/schema.json",
"plugin": ["typedoc-plugin-mdn-links", "typedoc-plugin-missing-exports", "typedoc-plugin-coverage"],
"coverageLabel": "TypeDoc",
"entryPoints": ["src/matrix.ts", "src/types.ts", "src/testing.ts", "src/utils/*.ts"],
"entryPoints": ["src/matrix.ts", "src/crypto-api", "src/types.ts", "src/testing.ts", "src/utils/*.ts"],
"excludeExternals": true,
"out": "_docs"
}
+31 -26
View File
@@ -1796,9 +1796,9 @@
undici-types "~5.26.4"
"@types/node@18":
version "18.19.50"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.50.tgz#8652b34ee7c0e7e2004b3f08192281808d41bf5a"
integrity sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==
version "18.19.54"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.54.tgz#f1048dc083f81b242640f04f18fb3e4ccf13fcdb"
integrity sha512-+BRgt0G5gYjTvdLac9sIeE0iZcJxi4Jc4PV5EUzqi+88jmQLr+fRZdv2tCTV7IHKSGxM6SaLoOXQWWUiLUItMw==
dependencies:
undici-types "~5.26.4"
@@ -2278,6 +2278,11 @@ babel-plugin-polyfill-regenerator@^0.6.1:
dependencies:
"@babel/helper-define-polyfill-provider" "^0.6.2"
babel-plugin-search-and-replace@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/babel-plugin-search-and-replace/-/babel-plugin-search-and-replace-1.1.1.tgz#2e5b4488e41d9eba1c220651b1a9b350fdf10915"
integrity sha512-fjP2VTF3mxxOUnc96mdK22llH92A6gu7A5AFapJmgnqsQi3bqLduIRP0FpA2r5vRZOYPpnX4rE5izQlpsMBjSA==
babel-preset-current-node-syntax@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b"
@@ -3128,9 +3133,9 @@ eslint-plugin-jest@^28.0.0:
"@typescript-eslint/utils" "^6.0.0 || ^7.0.0 || ^8.0.0"
eslint-plugin-jsdoc@^50.0.0:
version "50.2.4"
resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.2.4.tgz#9abef68ea5869e87d8a4444bfef9e5a7787162e2"
integrity sha512-020jA+dXaXdb+TML3ZJBvpPmzwbNROjnYuTYi/g6A5QEmEjhptz4oPJDKkOGMIByNxsPpdTLzSU1HYVqebOX1w==
version "50.3.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.3.1.tgz#4b56340a3ef55df9957dede430c6338c717dc0dc"
integrity sha512-SY9oUuTMr6aWoJggUS40LtMjsRzJPB5ZT7F432xZIHK3EfHF+8i48GbUBpwanrtlL9l1gILNTHK9o8gEhYLcKA==
dependencies:
"@es-joy/jsdoccomment" "~0.48.0"
are-docs-informative "^0.0.2"
@@ -3234,10 +3239,10 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
eslint-visitor-keys@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb"
integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==
eslint-visitor-keys@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz#1f785cc5e81eb7534523d85922248232077d2f8c"
integrity sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==
eslint@8.57.0:
version "8.57.0"
@@ -3284,13 +3289,13 @@ eslint@8.57.0:
text-table "^0.2.0"
espree@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/espree/-/espree-10.1.0.tgz#8788dae611574c0f070691f522e4116c5a11fc56"
integrity sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==
version "10.2.0"
resolved "https://registry.yarnpkg.com/espree/-/espree-10.2.0.tgz#f4bcead9e05b0615c968e85f83816bc386a45df6"
integrity sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==
dependencies:
acorn "^8.12.0"
acorn-jsx "^5.3.2"
eslint-visitor-keys "^4.0.0"
eslint-visitor-keys "^4.1.0"
espree@^9.6.0, espree@^9.6.1:
version "9.6.1"
@@ -3454,10 +3459,10 @@ fetch-mock-jest@^1.5.1:
dependencies:
fetch-mock "^9.11.0"
fetch-mock@11.1.3:
version "11.1.3"
resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-11.1.3.tgz#3dc8dcc81340fea1cf1db60a2c195c63e270b501"
integrity sha512-ATh0dWgnVrUHiiXuvQm1Ry+ThWfSv1QQgqJTCtybrNxyUrFiSOaDKsNG29eyysp1SHeNP6Q+dH50+8VifN51Ig==
fetch-mock@11.1.5:
version "11.1.5"
resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-11.1.5.tgz#77f78942f3733cfba47fc232b8528d1138a6761f"
integrity sha512-KHmZDnZ1ry0pCTrX4YG5DtThHi0MH+GNI9caESnzX/nMJBrvppUHMvLx47M0WY9oAtKOMiPfZDRpxhlHg89BOA==
dependencies:
"@types/glob-to-regexp" "^0.4.4"
dequal "^2.0.3"
@@ -4754,9 +4759,9 @@ kleur@^3.0.3:
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
knip@^5.0.0:
version "5.30.2"
resolved "https://registry.yarnpkg.com/knip/-/knip-5.30.2.tgz#3cee8df2e1eb0c858f81e64568b72c0a23b94479"
integrity sha512-UuUwuTN+6Aa6mjxufWwidayGX/tPJsxZSlwUo8q4R+Gf/0aNYuhHsUH/GccuKtRPfFnf3r+ZU/7BV0dNfC7OEQ==
version "5.30.6"
resolved "https://registry.yarnpkg.com/knip/-/knip-5.30.6.tgz#798dd6a7fe338025166b8b138bb183d6b8714b75"
integrity sha512-YkcnRVl0N99xZ7eaXE7KlH/4cPTCn6BGuk9KxINEdCMFN3yita2vGBizApy97ZOHgghy8tb589gQ3xvLMFIO4w==
dependencies:
"@nodelib/fs.walk" "1.2.8"
"@snyk/github-codeowners" "1.1.0"
@@ -4769,7 +4774,7 @@ knip@^5.0.0:
picocolors "^1.0.0"
picomatch "^4.0.1"
pretty-ms "^9.0.0"
smol-toml "^1.1.4"
smol-toml "^1.3.0"
strip-json-comments "5.0.1"
summary "2.1.0"
zod "^3.22.4"
@@ -5321,9 +5326,9 @@ parent-module@^1.0.0:
callsites "^3.0.0"
parse-imports@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/parse-imports/-/parse-imports-2.1.1.tgz#ce52141df24990065d72a446a364bffd595577f4"
integrity sha512-TDT4HqzUiTMO1wJRwg/t/hYk8Wdp3iF/ToMIlAoVQfL1Xs/sTxq1dKWSMjMbQmIarfWKymOyly40+zmPHXMqCA==
version "2.2.1"
resolved "https://registry.yarnpkg.com/parse-imports/-/parse-imports-2.2.1.tgz#0a6e8b5316beb5c9905f50eb2bbb8c64a4805642"
integrity sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==
dependencies:
es-module-lexer "^1.5.3"
slashes "^3.0.12"
@@ -5906,7 +5911,7 @@ slice-ansi@^7.1.0:
ansi-styles "^6.2.1"
is-fullwidth-code-point "^5.0.0"
smol-toml@^1.1.4:
smol-toml@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/smol-toml/-/smol-toml-1.3.0.tgz#5200e251fffadbb72570c84e9776d2a3eca48143"
integrity sha512-tWpi2TsODPScmi48b/OQZGi2lgUmBCHy6SZrhi/FdnnHiU1GwebbCfuQuxsC3nHaLwtYeJGPrDZDIeodDOc4pA==