You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-12-07 05:22:15 +03:00
Begin factoring out a CryptoBackend interface (#2955)
Part of https://github.com/vector-im/element-web/issues/21972. Eventually I want to replace the whole of the current `Crypto` implementation with an alternative implementation, but in order to get from here to there, I'm factoring out a common interface which will be implemented by both implementations. I'm also determined to fix the problem where the innards of the crypto implementation are exposed to applications via the `MatrixClient.crypto` property. It's not (yet) entirely clear what shape this interface should be, so I'm going with a minimal approach and adding things as we know we need them. This means that we need to keep the old `client.crypto` property around as well as a new `client.cryptoBackend` property. Eventually `client.crypto` will go away, but that will be a breaking change in the js-sdk.
This commit is contained in:
committed by
GitHub
parent
8293011ee2
commit
9c17eb6c14
@@ -650,6 +650,131 @@ describe("megolm", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("get|setGlobalErrorOnUnknownDevices", () => {
|
||||||
|
it("should raise an error if crypto is disabled", () => {
|
||||||
|
aliceTestClient.client["cryptoBackend"] = undefined;
|
||||||
|
expect(() => aliceTestClient.client.setGlobalErrorOnUnknownDevices(true)).toThrowError(
|
||||||
|
"encryption disabled",
|
||||||
|
);
|
||||||
|
expect(() => aliceTestClient.client.getGlobalErrorOnUnknownDevices()).toThrowError("encryption disabled");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should permit sending to unknown devices", async () => {
|
||||||
|
expect(aliceTestClient.client.getGlobalErrorOnUnknownDevices()).toBeTruthy();
|
||||||
|
|
||||||
|
aliceTestClient.expectKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
|
||||||
|
await aliceTestClient.start();
|
||||||
|
const p2pSession = await establishOlmSession(aliceTestClient, testOlmAccount);
|
||||||
|
|
||||||
|
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, getSyncResponse(["@bob:xyz"]));
|
||||||
|
await aliceTestClient.flushSync();
|
||||||
|
|
||||||
|
// start out with the device unknown - the send should be rejected.
|
||||||
|
aliceTestClient.httpBackend.when("POST", "/keys/query").respond(200, getTestKeysQueryResponse("@bob:xyz"));
|
||||||
|
aliceTestClient.httpBackend.when("POST", "/keys/query").respond(200, getTestKeysQueryResponse("@bob:xyz"));
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
aliceTestClient.client.sendTextMessage(ROOM_ID, "test").then(
|
||||||
|
() => {
|
||||||
|
throw new Error("sendTextMessage failed on an unknown device");
|
||||||
|
},
|
||||||
|
(e) => {
|
||||||
|
expect(e.name).toEqual("UnknownDeviceError");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
aliceTestClient.httpBackend.flushAllExpected(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// enable sending to unknown devices, and resend
|
||||||
|
aliceTestClient.client.setGlobalErrorOnUnknownDevices(false);
|
||||||
|
expect(aliceTestClient.client.getGlobalErrorOnUnknownDevices()).toBeFalsy();
|
||||||
|
|
||||||
|
const room = aliceTestClient.client.getRoom(ROOM_ID)!;
|
||||||
|
const pendingMsg = room.getPendingEvents()[0];
|
||||||
|
|
||||||
|
const inboundGroupSessionPromise = expectSendRoomKey(
|
||||||
|
aliceTestClient.httpBackend,
|
||||||
|
"@bob:xyz",
|
||||||
|
testOlmAccount,
|
||||||
|
p2pSession,
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
aliceTestClient.client.resendEvent(pendingMsg, room),
|
||||||
|
expectSendMegolmMessage(aliceTestClient.httpBackend, inboundGroupSessionPromise),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("get|setGlobalBlacklistUnverifiedDevices", () => {
|
||||||
|
it("should raise an error if crypto is disabled", () => {
|
||||||
|
aliceTestClient.client["cryptoBackend"] = undefined;
|
||||||
|
expect(() => aliceTestClient.client.setGlobalBlacklistUnverifiedDevices(true)).toThrowError(
|
||||||
|
"encryption disabled",
|
||||||
|
);
|
||||||
|
expect(() => aliceTestClient.client.getGlobalBlacklistUnverifiedDevices()).toThrowError(
|
||||||
|
"encryption disabled",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should disable sending to unverified devices", async () => {
|
||||||
|
aliceTestClient.expectKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
|
||||||
|
await aliceTestClient.start();
|
||||||
|
const p2pSession = await establishOlmSession(aliceTestClient, testOlmAccount);
|
||||||
|
|
||||||
|
// tell alice we share a room with bob
|
||||||
|
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, getSyncResponse(["@bob:xyz"]));
|
||||||
|
await aliceTestClient.flushSync();
|
||||||
|
|
||||||
|
logger.log("Forcing alice to download our device keys");
|
||||||
|
aliceTestClient.httpBackend.when("POST", "/keys/query").respond(200, getTestKeysQueryResponse("@bob:xyz"));
|
||||||
|
aliceTestClient.httpBackend.when("POST", "/keys/query").respond(200, getTestKeysQueryResponse("@bob:xyz"));
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
aliceTestClient.client.downloadKeys(["@bob:xyz"]),
|
||||||
|
aliceTestClient.httpBackend.flush("/keys/query", 2),
|
||||||
|
]);
|
||||||
|
|
||||||
|
logger.log("Telling alice to block messages to unverified devices");
|
||||||
|
expect(aliceTestClient.client.getGlobalBlacklistUnverifiedDevices()).toBeFalsy();
|
||||||
|
aliceTestClient.client.setGlobalBlacklistUnverifiedDevices(true);
|
||||||
|
expect(aliceTestClient.client.getGlobalBlacklistUnverifiedDevices()).toBeTruthy();
|
||||||
|
|
||||||
|
logger.log("Telling alice to send a megolm message");
|
||||||
|
aliceTestClient.httpBackend.when("PUT", "/send/").respond(200, { event_id: "$event_id" });
|
||||||
|
aliceTestClient.httpBackend.when("PUT", "/sendToDevice/m.room_key.withheld/").respond(200, {});
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
aliceTestClient.client.sendTextMessage(ROOM_ID, "test"),
|
||||||
|
aliceTestClient.httpBackend.flushAllExpected({ timeout: 1000 }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Now, let's mark the device as verified, and check that keys are sent to it.
|
||||||
|
|
||||||
|
logger.log("Marking the device as verified");
|
||||||
|
// XXX: this is an integration test; we really ought to do this via the cross-signing dance
|
||||||
|
const d = aliceTestClient.client.crypto!.deviceList.getStoredDevice("@bob:xyz", "DEVICE_ID")!;
|
||||||
|
d.verified = DeviceInfo.DeviceVerification.VERIFIED;
|
||||||
|
aliceTestClient.client.crypto?.deviceList.storeDevicesForUser("@bob:xyz", { DEVICE_ID: d });
|
||||||
|
|
||||||
|
const inboundGroupSessionPromise = expectSendRoomKey(
|
||||||
|
aliceTestClient.httpBackend,
|
||||||
|
"@bob:xyz",
|
||||||
|
testOlmAccount,
|
||||||
|
p2pSession,
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.log("Asking alice to re-send");
|
||||||
|
await Promise.all([
|
||||||
|
expectSendMegolmMessage(aliceTestClient.httpBackend, inboundGroupSessionPromise).then((decrypted) => {
|
||||||
|
expect(decrypted.type).toEqual("m.room.message");
|
||||||
|
expect(decrypted.content!.body).toEqual("test");
|
||||||
|
}),
|
||||||
|
aliceTestClient.client.sendTextMessage(ROOM_ID, "test"),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("We should start a new megolm session when a device is blocked", async () => {
|
it("We should start a new megolm session when a device is blocked", async () => {
|
||||||
aliceTestClient.expectKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
|
aliceTestClient.expectKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
|
||||||
await aliceTestClient.start();
|
await aliceTestClient.start();
|
||||||
|
|||||||
@@ -1147,7 +1147,7 @@ describe("userHasCrossSigningKeys", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("throws an error if crypto is disabled", () => {
|
it("throws an error if crypto is disabled", () => {
|
||||||
aliceClient.crypto = undefined;
|
aliceClient["cryptoBackend"] = undefined;
|
||||||
expect(() => aliceClient.userHasCrossSigningKeys()).toThrowError("encryption disabled");
|
expect(() => aliceClient.userHasCrossSigningKeys()).toThrowError("encryption disabled");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3131,7 +3131,7 @@ describe("Room", function () {
|
|||||||
|
|
||||||
it("should load pending events from from the store and decrypt if needed", async () => {
|
it("should load pending events from from the store and decrypt if needed", async () => {
|
||||||
const client = new TestClient(userA).client;
|
const client = new TestClient(userA).client;
|
||||||
client.crypto = {
|
client.crypto = client["cryptoBackend"] = {
|
||||||
decryptEvent: jest.fn().mockResolvedValue({ clearEvent: { body: "enc" } }),
|
decryptEvent: jest.fn().mockResolvedValue({ clearEvent: { body: "enc" } }),
|
||||||
} as unknown as Crypto;
|
} as unknown as Crypto;
|
||||||
client.store.getPendingEvents = jest.fn(async (roomId) => [
|
client.store.getPendingEvents = jest.fn(async (roomId) => [
|
||||||
|
|||||||
@@ -14,7 +14,33 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { IClearEvent } from "../models/event";
|
||||||
|
|
||||||
export type OlmGroupSessionExtraData = {
|
export type OlmGroupSessionExtraData = {
|
||||||
untrusted?: boolean;
|
untrusted?: boolean;
|
||||||
sharedHistory?: boolean;
|
sharedHistory?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of a (successful) call to {@link Crypto.decryptEvent}
|
||||||
|
*/
|
||||||
|
export interface IEventDecryptionResult {
|
||||||
|
/**
|
||||||
|
* The plaintext payload for the event (typically containing <tt>type</tt> and <tt>content</tt> fields).
|
||||||
|
*/
|
||||||
|
clearEvent: IClearEvent;
|
||||||
|
/**
|
||||||
|
* List of curve25519 keys involved in telling us about the senderCurve25519Key and claimedEd25519Key.
|
||||||
|
* See {@link MatrixEvent#getForwardingCurve25519KeyChain}.
|
||||||
|
*/
|
||||||
|
forwardingCurve25519KeyChain?: string[];
|
||||||
|
/**
|
||||||
|
* Key owned by the sender of this event. See {@link MatrixEvent#getSenderKey}.
|
||||||
|
*/
|
||||||
|
senderCurve25519Key?: string;
|
||||||
|
/**
|
||||||
|
* ed25519 key claimed by the sender of this event. See {@link MatrixEvent#getClaimedEd25519Key}.
|
||||||
|
*/
|
||||||
|
claimedEd25519Key?: string;
|
||||||
|
untrusted?: boolean;
|
||||||
|
}
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ import { UIARequest, UIAResponse } from "./@types/uia";
|
|||||||
import { LocalNotificationSettings } from "./@types/local_notifications";
|
import { LocalNotificationSettings } from "./@types/local_notifications";
|
||||||
import { UNREAD_THREAD_NOTIFICATIONS } from "./@types/sync";
|
import { UNREAD_THREAD_NOTIFICATIONS } from "./@types/sync";
|
||||||
import { buildFeatureSupportMap, Feature, ServerSupport } from "./feature";
|
import { buildFeatureSupportMap, Feature, ServerSupport } from "./feature";
|
||||||
|
import { CryptoBackend } from "./common-crypto/CryptoBackend";
|
||||||
|
|
||||||
export type Store = IStore;
|
export type Store = IStore;
|
||||||
|
|
||||||
@@ -1147,7 +1148,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
public urlPreviewCache: { [key: string]: Promise<IPreviewUrlResponse> } = {};
|
public urlPreviewCache: { [key: string]: Promise<IPreviewUrlResponse> } = {};
|
||||||
public identityServer?: IIdentityServerProvider;
|
public identityServer?: IIdentityServerProvider;
|
||||||
public http: MatrixHttpApi<IHttpOpts & { onlyData: true }>; // XXX: Intended private, used in code.
|
public http: MatrixHttpApi<IHttpOpts & { onlyData: true }>; // XXX: Intended private, used in code.
|
||||||
public crypto?: Crypto; // XXX: Intended private, used in code.
|
public crypto?: Crypto; // libolm crypto implementation. XXX: Intended private, used in code. Being replaced by cryptoBackend
|
||||||
|
private cryptoBackend?: CryptoBackend; // one of crypto or rustCrypto
|
||||||
public cryptoCallbacks: ICryptoCallbacks; // XXX: Intended private, used in code.
|
public cryptoCallbacks: ICryptoCallbacks; // XXX: Intended private, used in code.
|
||||||
public callEventHandler?: CallEventHandler; // XXX: Intended private, used in code.
|
public callEventHandler?: CallEventHandler; // XXX: Intended private, used in code.
|
||||||
public groupCallEventHandler?: GroupCallEventHandler;
|
public groupCallEventHandler?: GroupCallEventHandler;
|
||||||
@@ -1455,7 +1457,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* clean shutdown.
|
* clean shutdown.
|
||||||
*/
|
*/
|
||||||
public stopClient(): void {
|
public stopClient(): void {
|
||||||
this.crypto?.stop(); // crypto might have been initialised even if the client wasn't fully started
|
this.cryptoBackend?.stop(); // crypto might have been initialised even if the client wasn't fully started
|
||||||
|
|
||||||
if (!this.clientRunning) return; // already stopped
|
if (!this.clientRunning) return; // already stopped
|
||||||
|
|
||||||
@@ -1959,7 +1961,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialise support for end-to-end encryption in this client
|
* Initialise support for end-to-end encryption in this client, using libolm.
|
||||||
*
|
*
|
||||||
* You should call this method after creating the matrixclient, but *before*
|
* You should call this method after creating the matrixclient, but *before*
|
||||||
* calling `startClient`, if you want to support end-to-end encryption.
|
* calling `startClient`, if you want to support end-to-end encryption.
|
||||||
@@ -1975,7 +1977,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.crypto) {
|
if (this.cryptoBackend) {
|
||||||
logger.warn("Attempt to re-initialise e2e encryption on MatrixClient");
|
logger.warn("Attempt to re-initialise e2e encryption on MatrixClient");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2040,7 +2042,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
|
|
||||||
// if crypto initialisation was successful, tell it to attach its event handlers.
|
// if crypto initialisation was successful, tell it to attach its event handlers.
|
||||||
crypto.registerEventHandlers(this as Parameters<Crypto["registerEventHandlers"]>[0]);
|
crypto.registerEventHandlers(this as Parameters<Crypto["registerEventHandlers"]>[0]);
|
||||||
this.crypto = crypto;
|
this.cryptoBackend = this.crypto = crypto;
|
||||||
|
|
||||||
// upload our keys in the background
|
// upload our keys in the background
|
||||||
this.crypto.uploadDeviceKeys().catch((e) => {
|
this.crypto.uploadDeviceKeys().catch((e) => {
|
||||||
@@ -2054,7 +2056,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* @returns True if end-to-end is enabled.
|
* @returns True if end-to-end is enabled.
|
||||||
*/
|
*/
|
||||||
public isCryptoEnabled(): boolean {
|
public isCryptoEnabled(): boolean {
|
||||||
return !!this.crypto;
|
return !!this.cryptoBackend;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2299,10 +2301,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* @param value - whether to blacklist all unverified devices by default
|
* @param value - whether to blacklist all unverified devices by default
|
||||||
*/
|
*/
|
||||||
public setGlobalBlacklistUnverifiedDevices(value: boolean): boolean {
|
public setGlobalBlacklistUnverifiedDevices(value: boolean): boolean {
|
||||||
if (!this.crypto) {
|
if (!this.cryptoBackend) {
|
||||||
throw new Error("End-to-end encryption disabled");
|
throw new Error("End-to-end encryption disabled");
|
||||||
}
|
}
|
||||||
this.crypto.globalBlacklistUnverifiedDevices = value;
|
this.cryptoBackend.globalBlacklistUnverifiedDevices = value;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2310,10 +2312,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* @returns whether to blacklist all unverified devices by default
|
* @returns whether to blacklist all unverified devices by default
|
||||||
*/
|
*/
|
||||||
public getGlobalBlacklistUnverifiedDevices(): boolean {
|
public getGlobalBlacklistUnverifiedDevices(): boolean {
|
||||||
if (!this.crypto) {
|
if (!this.cryptoBackend) {
|
||||||
throw new Error("End-to-end encryption disabled");
|
throw new Error("End-to-end encryption disabled");
|
||||||
}
|
}
|
||||||
return this.crypto.globalBlacklistUnverifiedDevices;
|
return this.cryptoBackend.globalBlacklistUnverifiedDevices;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2327,10 +2329,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* @param value - whether error on unknown devices
|
* @param value - whether error on unknown devices
|
||||||
*/
|
*/
|
||||||
public setGlobalErrorOnUnknownDevices(value: boolean): void {
|
public setGlobalErrorOnUnknownDevices(value: boolean): void {
|
||||||
if (!this.crypto) {
|
if (!this.cryptoBackend) {
|
||||||
throw new Error("End-to-end encryption disabled");
|
throw new Error("End-to-end encryption disabled");
|
||||||
}
|
}
|
||||||
this.crypto.globalErrorOnUnknownDevices = value;
|
this.cryptoBackend.globalErrorOnUnknownDevices = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2339,10 +2341,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* This API is currently UNSTABLE and may change or be removed without notice.
|
* This API is currently UNSTABLE and may change or be removed without notice.
|
||||||
*/
|
*/
|
||||||
public getGlobalErrorOnUnknownDevices(): boolean {
|
public getGlobalErrorOnUnknownDevices(): boolean {
|
||||||
if (!this.crypto) {
|
if (!this.cryptoBackend) {
|
||||||
throw new Error("End-to-end encryption disabled");
|
throw new Error("End-to-end encryption disabled");
|
||||||
}
|
}
|
||||||
return this.crypto.globalErrorOnUnknownDevices;
|
return this.cryptoBackend.globalErrorOnUnknownDevices;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2482,10 +2484,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* the cross-signing pseudo-device.
|
* the cross-signing pseudo-device.
|
||||||
*/
|
*/
|
||||||
public userHasCrossSigningKeys(): Promise<boolean> {
|
public userHasCrossSigningKeys(): Promise<boolean> {
|
||||||
if (!this.crypto) {
|
if (!this.cryptoBackend) {
|
||||||
throw new Error("End-to-end encryption disabled");
|
throw new Error("End-to-end encryption disabled");
|
||||||
}
|
}
|
||||||
return this.crypto.userHasCrossSigningKeys();
|
return this.cryptoBackend.userHasCrossSigningKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -7162,7 +7164,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
*/
|
*/
|
||||||
public decryptEventIfNeeded(event: MatrixEvent, options?: IDecryptOptions): Promise<void> {
|
public decryptEventIfNeeded(event: MatrixEvent, options?: IDecryptOptions): Promise<void> {
|
||||||
if (event.shouldAttemptDecryption() && this.isCryptoEnabled()) {
|
if (event.shouldAttemptDecryption() && this.isCryptoEnabled()) {
|
||||||
event.attemptDecryption(this.crypto!, options);
|
event.attemptDecryption(this.cryptoBackend!, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.isBeingDecrypted()) {
|
if (event.isBeingDecrypted()) {
|
||||||
|
|||||||
63
src/common-crypto/CryptoBackend.ts
Normal file
63
src/common-crypto/CryptoBackend.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { IEventDecryptionResult } from "../@types/crypto";
|
||||||
|
import { MatrixEvent } from "../models/event";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common interface for the crypto implementations
|
||||||
|
*/
|
||||||
|
export interface CryptoBackend {
|
||||||
|
/**
|
||||||
|
* Global override for whether the client should ever send encrypted
|
||||||
|
* messages to unverified devices. This provides the default for rooms which
|
||||||
|
* do not specify a value.
|
||||||
|
*
|
||||||
|
* If true, all unverified devices will be blacklisted by default
|
||||||
|
*/
|
||||||
|
globalBlacklistUnverifiedDevices: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether sendMessage in a room with unknown and unverified devices
|
||||||
|
* should throw an error and not send the message. This has 'Global' for
|
||||||
|
* symmetry with setGlobalBlacklistUnverifiedDevices but there is currently
|
||||||
|
* no room-level equivalent for this setting.
|
||||||
|
*/
|
||||||
|
globalErrorOnUnknownDevices: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shut down any background processes related to crypto
|
||||||
|
*/
|
||||||
|
stop(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the user has previously published cross-signing keys
|
||||||
|
*
|
||||||
|
* This means downloading the devicelist for the user and checking if the list includes
|
||||||
|
* the cross-signing pseudo-device.
|
||||||
|
|
||||||
|
* @returns true if the user has previously published cross-signing keys
|
||||||
|
*/
|
||||||
|
userHasCrossSigningKeys(): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt a received event
|
||||||
|
*
|
||||||
|
* @returns a promise which resolves once we have finished decrypting.
|
||||||
|
* Rejects with an error if there is a problem decrypting the event.
|
||||||
|
*/
|
||||||
|
decryptEvent(event: MatrixEvent): Promise<IEventDecryptionResult>;
|
||||||
|
}
|
||||||
4
src/common-crypto/README.md
Normal file
4
src/common-crypto/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
This directory contains functionality which is common to both the legacy (libolm-based) crypto implementation,
|
||||||
|
and the new rust-based implementation.
|
||||||
|
|
||||||
|
It is an internal module, and is _not_ directly exposed to applications.
|
||||||
@@ -20,6 +20,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
|
import type { IEventDecryptionResult } from "../../@types/crypto";
|
||||||
import { logger } from "../../logger";
|
import { logger } from "../../logger";
|
||||||
import * as olmlib from "../olmlib";
|
import * as olmlib from "../olmlib";
|
||||||
import {
|
import {
|
||||||
@@ -38,13 +39,7 @@ import { IOlmSessionResult } from "../olmlib";
|
|||||||
import { DeviceInfoMap } from "../DeviceList";
|
import { DeviceInfoMap } from "../DeviceList";
|
||||||
import { IContent, MatrixEvent } from "../../models/event";
|
import { IContent, MatrixEvent } from "../../models/event";
|
||||||
import { EventType, MsgType, ToDeviceMessageId } from "../../@types/event";
|
import { EventType, MsgType, ToDeviceMessageId } from "../../@types/event";
|
||||||
import {
|
import { IMegolmEncryptedContent, IMegolmSessionData, IncomingRoomKeyRequest, IEncryptedContent } from "../index";
|
||||||
IMegolmEncryptedContent,
|
|
||||||
IEventDecryptionResult,
|
|
||||||
IMegolmSessionData,
|
|
||||||
IncomingRoomKeyRequest,
|
|
||||||
IEncryptedContent,
|
|
||||||
} from "../index";
|
|
||||||
import { RoomKeyRequestState } from "../OutgoingRoomKeyRequestManager";
|
import { RoomKeyRequestState } from "../OutgoingRoomKeyRequestManager";
|
||||||
import { OlmGroupSessionExtraData } from "../../@types/crypto";
|
import { OlmGroupSessionExtraData } from "../../@types/crypto";
|
||||||
import { MatrixError } from "../../http-api";
|
import { MatrixError } from "../../http-api";
|
||||||
|
|||||||
@@ -18,13 +18,14 @@ limitations under the License.
|
|||||||
* Defines m.olm encryption/decryption
|
* Defines m.olm encryption/decryption
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { IEventDecryptionResult } from "../../@types/crypto";
|
||||||
import { logger } from "../../logger";
|
import { logger } from "../../logger";
|
||||||
import * as olmlib from "../olmlib";
|
import * as olmlib from "../olmlib";
|
||||||
import { DeviceInfo } from "../deviceinfo";
|
import { DeviceInfo } from "../deviceinfo";
|
||||||
import { DecryptionAlgorithm, DecryptionError, EncryptionAlgorithm, registerAlgorithm } from "./base";
|
import { DecryptionAlgorithm, DecryptionError, EncryptionAlgorithm, registerAlgorithm } from "./base";
|
||||||
import { Room } from "../../models/room";
|
import { Room } from "../../models/room";
|
||||||
import { IContent, MatrixEvent } from "../../models/event";
|
import { IContent, MatrixEvent } from "../../models/event";
|
||||||
import { IEncryptedContent, IEventDecryptionResult, IOlmEncryptedContent } from "../index";
|
import { IEncryptedContent, IOlmEncryptedContent } from "../index";
|
||||||
import { IInboundSession } from "../OlmDevice";
|
import { IInboundSession } from "../OlmDevice";
|
||||||
|
|
||||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ limitations under the License.
|
|||||||
import anotherjson from "another-json";
|
import anotherjson from "another-json";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
|
import type { IEventDecryptionResult } from "../@types/crypto";
|
||||||
import type { PkDecryption, PkSigning } from "@matrix-org/olm";
|
import type { PkDecryption, PkSigning } from "@matrix-org/olm";
|
||||||
import { EventType, ToDeviceMessageId } from "../@types/event";
|
import { EventType, ToDeviceMessageId } from "../@types/event";
|
||||||
import { TypedReEmitter } from "../ReEmitter";
|
import { TypedReEmitter } from "../ReEmitter";
|
||||||
@@ -67,7 +68,7 @@ import { BackupManager } from "./backup";
|
|||||||
import { IStore } from "../store";
|
import { IStore } from "../store";
|
||||||
import { Room, RoomEvent } from "../models/room";
|
import { Room, RoomEvent } from "../models/room";
|
||||||
import { RoomMember, RoomMemberEvent } from "../models/room-member";
|
import { RoomMember, RoomMemberEvent } from "../models/room-member";
|
||||||
import { EventStatus, IClearEvent, IEvent, MatrixEvent, MatrixEventEvent } from "../models/event";
|
import { EventStatus, IEvent, MatrixEvent, MatrixEventEvent } from "../models/event";
|
||||||
import { ToDeviceBatch } from "../models/ToDeviceMessage";
|
import { ToDeviceBatch } from "../models/ToDeviceMessage";
|
||||||
import {
|
import {
|
||||||
ClientEvent,
|
ClientEvent,
|
||||||
@@ -87,6 +88,7 @@ import { IContent } from "../models/event";
|
|||||||
import { ISyncResponse } from "../sync-accumulator";
|
import { ISyncResponse } from "../sync-accumulator";
|
||||||
import { ISignatures } from "../@types/signed";
|
import { ISignatures } from "../@types/signed";
|
||||||
import { IMessage } from "./algorithms/olm";
|
import { IMessage } from "./algorithms/olm";
|
||||||
|
import { CryptoBackend } from "../common-crypto/CryptoBackend";
|
||||||
|
|
||||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||||
|
|
||||||
@@ -218,30 +220,6 @@ interface ISignableObject {
|
|||||||
unsigned?: object;
|
unsigned?: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The result of a (successful) call to decryptEvent.
|
|
||||||
*/
|
|
||||||
export interface IEventDecryptionResult {
|
|
||||||
/**
|
|
||||||
* The plaintext payload for the event (typically containing <tt>type</tt> and <tt>content</tt> fields).
|
|
||||||
*/
|
|
||||||
clearEvent: IClearEvent;
|
|
||||||
/**
|
|
||||||
* List of curve25519 keys involved in telling us about the senderCurve25519Key and claimedEd25519Key.
|
|
||||||
* See {@link MatrixEvent#getForwardingCurve25519KeyChain}.
|
|
||||||
*/
|
|
||||||
forwardingCurve25519KeyChain?: string[];
|
|
||||||
/**
|
|
||||||
* Key owned by the sender of this event. See {@link MatrixEvent#getSenderKey}.
|
|
||||||
*/
|
|
||||||
senderCurve25519Key?: string;
|
|
||||||
/**
|
|
||||||
* ed25519 key claimed by the sender of this event. See {@link MatrixEvent#getClaimedEd25519Key}.
|
|
||||||
*/
|
|
||||||
claimedEd25519Key?: string;
|
|
||||||
untrusted?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IRequestsMap {
|
export interface IRequestsMap {
|
||||||
getRequest(event: MatrixEvent): VerificationRequest | undefined;
|
getRequest(event: MatrixEvent): VerificationRequest | undefined;
|
||||||
getRequestByChannel(channel: IVerificationChannel): VerificationRequest | undefined;
|
getRequestByChannel(channel: IVerificationChannel): VerificationRequest | undefined;
|
||||||
@@ -380,7 +358,7 @@ export type CryptoEventHandlerMap = {
|
|||||||
[CryptoEvent.UserCrossSigningUpdated]: (userId: string) => void;
|
[CryptoEvent.UserCrossSigningUpdated]: (userId: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap> {
|
export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap> implements CryptoBackend {
|
||||||
/**
|
/**
|
||||||
* @returns The version of Olm.
|
* @returns The version of Olm.
|
||||||
*/
|
*/
|
||||||
@@ -3915,3 +3893,6 @@ class IncomingRoomKeyRequestCancellation {
|
|||||||
this.requestId = content.request_id;
|
this.requestId = content.request_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IEventDecryptionResult is re-exported for backwards compatibility, in case any applications are referencing it.
|
||||||
|
export type { IEventDecryptionResult } from "../@types/crypto";
|
||||||
|
|||||||
@@ -21,10 +21,11 @@ limitations under the License.
|
|||||||
|
|
||||||
import { ExtensibleEvent, ExtensibleEvents, Optional } from "matrix-events-sdk";
|
import { ExtensibleEvent, ExtensibleEvents, Optional } from "matrix-events-sdk";
|
||||||
|
|
||||||
|
import type { IEventDecryptionResult } from "../@types/crypto";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
import { VerificationRequest } from "../crypto/verification/request/VerificationRequest";
|
import { VerificationRequest } from "../crypto/verification/request/VerificationRequest";
|
||||||
import { EVENT_VISIBILITY_CHANGE_TYPE, EventType, MsgType, RelationType } from "../@types/event";
|
import { EVENT_VISIBILITY_CHANGE_TYPE, EventType, MsgType, RelationType } from "../@types/event";
|
||||||
import { Crypto, IEventDecryptionResult } from "../crypto";
|
import { Crypto } from "../crypto";
|
||||||
import { deepSortedObjectEntries, internaliseString } from "../utils";
|
import { deepSortedObjectEntries, internaliseString } from "../utils";
|
||||||
import { RoomMember } from "./room-member";
|
import { RoomMember } from "./room-member";
|
||||||
import { Thread, ThreadEvent, EventHandlerMap as ThreadEventHandlerMap, THREAD_RELATION_TYPE } from "./thread";
|
import { Thread, ThreadEvent, EventHandlerMap as ThreadEventHandlerMap, THREAD_RELATION_TYPE } from "./thread";
|
||||||
@@ -34,6 +35,7 @@ import { MatrixError } from "../http-api";
|
|||||||
import { TypedEventEmitter } from "./typed-event-emitter";
|
import { TypedEventEmitter } from "./typed-event-emitter";
|
||||||
import { EventStatus } from "./event-status";
|
import { EventStatus } from "./event-status";
|
||||||
import { DecryptionError } from "../crypto/algorithms";
|
import { DecryptionError } from "../crypto/algorithms";
|
||||||
|
import { CryptoBackend } from "../common-crypto/CryptoBackend";
|
||||||
|
|
||||||
export { EventStatus } from "./event-status";
|
export { EventStatus } from "./event-status";
|
||||||
|
|
||||||
@@ -725,7 +727,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
|||||||
* @returns promise which resolves (to undefined) when the decryption
|
* @returns promise which resolves (to undefined) when the decryption
|
||||||
* attempt is completed.
|
* attempt is completed.
|
||||||
*/
|
*/
|
||||||
public async attemptDecryption(crypto: Crypto, options: IDecryptOptions = {}): Promise<void> {
|
public async attemptDecryption(crypto: CryptoBackend, options: IDecryptOptions = {}): Promise<void> {
|
||||||
// start with a couple of sanity checks.
|
// start with a couple of sanity checks.
|
||||||
if (!this.isEncrypted()) {
|
if (!this.isEncrypted()) {
|
||||||
throw new Error("Attempt to decrypt event which isn't encrypted");
|
throw new Error("Attempt to decrypt event which isn't encrypted");
|
||||||
@@ -803,7 +805,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
|||||||
return recipients;
|
return recipients;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async decryptionLoop(crypto: Crypto, options: IDecryptOptions = {}): Promise<void> {
|
private async decryptionLoop(crypto: CryptoBackend, options: IDecryptOptions = {}): Promise<void> {
|
||||||
// make sure that this method never runs completely synchronously.
|
// make sure that this method never runs completely synchronously.
|
||||||
// (doing so would mean that we would clear decryptionPromise *before*
|
// (doing so would mean that we would clear decryptionPromise *before*
|
||||||
// it is set in attemptDecryption - and hence end up with a stuck
|
// it is set in attemptDecryption - and hence end up with a stuck
|
||||||
|
|||||||
Reference in New Issue
Block a user