1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-23 17:02:25 +03:00

rust impl of getEncryptionInfoForEvent (#3718)

This commit is contained in:
Richard van der Hoff
2023-09-18 16:49:24 +02:00
committed by GitHub
parent 5e542b3869
commit cb375e1351
2 changed files with 185 additions and 13 deletions

View File

@@ -30,6 +30,7 @@ import {
IHttpOpts, IHttpOpts,
IToDeviceEvent, IToDeviceEvent,
MatrixClient, MatrixClient,
MatrixEvent,
MatrixHttpApi, MatrixHttpApi,
TypedEventEmitter, TypedEventEmitter,
} from "../../../src"; } from "../../../src";
@@ -38,7 +39,13 @@ import { CryptoBackend } from "../../../src/common-crypto/CryptoBackend";
import { IEventDecryptionResult } from "../../../src/@types/crypto"; import { IEventDecryptionResult } from "../../../src/@types/crypto";
import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor"; import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
import { ServerSideSecretStorage } from "../../../src/secret-storage"; import { ServerSideSecretStorage } from "../../../src/secret-storage";
import { CryptoCallbacks, ImportRoomKeysOpts, VerificationRequest } from "../../../src/crypto-api"; import {
CryptoCallbacks,
EventShieldColour,
EventShieldReason,
ImportRoomKeysOpts,
VerificationRequest,
} from "../../../src/crypto-api";
import * as testData from "../../test-utils/test-data"; import * as testData from "../../test-utils/test-data";
import { defer } from "../../../src/utils"; import { defer } from "../../../src/utils";
@@ -373,6 +380,111 @@ describe("RustCrypto", () => {
}); });
}); });
describe(".getEncryptionInfoForEvent", () => {
let rustCrypto: RustCrypto;
let olmMachine: Mocked<RustSdkCryptoJs.OlmMachine>;
beforeEach(() => {
olmMachine = {
getRoomEventEncryptionInfo: jest.fn(),
} as unknown as Mocked<RustSdkCryptoJs.OlmMachine>;
rustCrypto = new RustCrypto(
olmMachine,
{} as MatrixClient["http"],
TEST_USER,
TEST_DEVICE_ID,
{} as ServerSideSecretStorage,
{} as CryptoCallbacks,
);
});
afterEach(() => {
jest.restoreAllMocks();
});
async function makeEncryptedEvent(): Promise<MatrixEvent> {
const encryptedEvent = mkEvent({
event: true,
type: "m.room.encrypted",
content: { algorithm: "fake_alg" },
room: "!room:id",
});
encryptedEvent.event.event_id = "$event:id";
const mockCryptoBackend = {
decryptEvent: () =>
({
clearEvent: { content: { body: "1234" } },
} as unknown as IEventDecryptionResult),
} as unknown as CryptoBackend;
await encryptedEvent.attemptDecryption(mockCryptoBackend);
return encryptedEvent;
}
it("should handle unencrypted events", async () => {
const event = mkEvent({ event: true, type: "m.room.message", content: { body: "xyz" } });
const res = await rustCrypto.getEncryptionInfoForEvent(event);
expect(res).toBe(null);
expect(olmMachine.getRoomEventEncryptionInfo).not.toHaveBeenCalled();
});
it("passes the event into the OlmMachine", async () => {
const encryptedEvent = await makeEncryptedEvent();
const res = await rustCrypto.getEncryptionInfoForEvent(encryptedEvent);
expect(res).toBe(null);
expect(olmMachine.getRoomEventEncryptionInfo).toHaveBeenCalledTimes(1);
const [passedEvent, passedRoom] = olmMachine.getRoomEventEncryptionInfo.mock.calls[0];
expect(passedRoom.toString()).toEqual("!room:id");
expect(JSON.parse(passedEvent)).toStrictEqual(
expect.objectContaining({
event_id: "$event:id",
}),
);
});
it.each([
[RustSdkCryptoJs.ShieldColor.None, EventShieldColour.NONE],
[RustSdkCryptoJs.ShieldColor.Grey, EventShieldColour.GREY],
[RustSdkCryptoJs.ShieldColor.Red, EventShieldColour.RED],
])("gets the right shield color (%i)", async (rustShield, expectedShield) => {
const mockEncryptionInfo = {
shieldState: jest.fn().mockReturnValue({ color: rustShield, message: null }),
} as unknown as RustSdkCryptoJs.EncryptionInfo;
olmMachine.getRoomEventEncryptionInfo.mockResolvedValue(mockEncryptionInfo);
const res = await rustCrypto.getEncryptionInfoForEvent(await makeEncryptedEvent());
expect(mockEncryptionInfo.shieldState).toHaveBeenCalledWith(false);
expect(res).not.toBe(null);
expect(res!.shieldColour).toEqual(expectedShield);
});
it.each([
[null, null],
["Encrypted by an unverified user.", EventShieldReason.UNVERIFIED_IDENTITY],
["Encrypted by a device not verified by its owner.", EventShieldReason.UNSIGNED_DEVICE],
[
"The authenticity of this encrypted message can't be guaranteed on this device.",
EventShieldReason.AUTHENTICITY_NOT_GUARANTEED,
],
["Encrypted by an unknown or deleted device.", EventShieldReason.UNKNOWN_DEVICE],
["bloop", EventShieldReason.UNKNOWN],
])("gets the right shield reason (%s)", async (rustReason, expectedReason) => {
// suppress the warning from the unknown shield reason
jest.spyOn(console, "warn").mockImplementation(() => {});
const mockEncryptionInfo = {
shieldState: jest
.fn()
.mockReturnValue({ color: RustSdkCryptoJs.ShieldColor.None, message: rustReason }),
} as unknown as RustSdkCryptoJs.EncryptionInfo;
olmMachine.getRoomEventEncryptionInfo.mockResolvedValue(mockEncryptionInfo);
const res = await rustCrypto.getEncryptionInfoForEvent(await makeEncryptedEvent());
expect(mockEncryptionInfo.shieldState).toHaveBeenCalledWith(false);
expect(res).not.toBe(null);
expect(res!.shieldReason).toEqual(expectedReason);
});
});
describe("get|setTrustCrossSignedDevices", () => { describe("get|setTrustCrossSignedDevices", () => {
let rustCrypto: RustCrypto; let rustCrypto: RustCrypto;

View File

@@ -42,6 +42,7 @@ import {
DeviceVerificationStatus, DeviceVerificationStatus,
EventEncryptionInfo, EventEncryptionInfo,
EventShieldColour, EventShieldColour,
EventShieldReason,
GeneratedSecretStorageKey, GeneratedSecretStorageKey,
ImportRoomKeyProgressData, ImportRoomKeyProgressData,
ImportRoomKeysOpts, ImportRoomKeysOpts,
@@ -788,10 +789,7 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
* Implementation of {@link CryptoApi.getEncryptionInfoForEvent}. * Implementation of {@link CryptoApi.getEncryptionInfoForEvent}.
*/ */
public async getEncryptionInfoForEvent(event: MatrixEvent): Promise<EventEncryptionInfo | null> { public async getEncryptionInfoForEvent(event: MatrixEvent): Promise<EventEncryptionInfo | null> {
return { return this.eventDecryptor.getEncryptionInfoForEvent(event);
shieldColour: EventShieldColour.NONE,
shieldReason: null,
};
} }
/** /**
@@ -1484,14 +1482,7 @@ class EventDecryptor {
try { try {
const res = (await this.olmMachine.decryptRoomEvent( const res = (await this.olmMachine.decryptRoomEvent(
JSON.stringify({ stringifyEvent(event),
event_id: event.getId(),
type: event.getWireType(),
sender: event.getSender(),
state_key: event.getStateKey(),
content: event.getWireContent(),
origin_server_ts: event.getTs(),
}),
new RustSdkCryptoJs.RoomId(event.getRoomId()!), new RustSdkCryptoJs.RoomId(event.getRoomId()!),
)) as RustSdkCryptoJs.DecryptedRoomEvent; )) as RustSdkCryptoJs.DecryptedRoomEvent;
@@ -1549,6 +1540,20 @@ class EventDecryptor {
} }
} }
public async getEncryptionInfoForEvent(event: MatrixEvent): Promise<EventEncryptionInfo | null> {
if (!event.getClearContent()) {
// not successfully decrypted
return null;
}
const encryptionInfo = await this.olmMachine.getRoomEventEncryptionInfo(
stringifyEvent(event),
new RustSdkCryptoJs.RoomId(event.getRoomId()!),
);
return rustEncryptionInfoToJsEncryptionInfo(encryptionInfo);
}
/** /**
* Look for events which are waiting for a given megolm session * Look for events which are waiting for a given megolm session
* *
@@ -1606,6 +1611,61 @@ class EventDecryptor {
} }
} }
function stringifyEvent(event: MatrixEvent): string {
return JSON.stringify({
event_id: event.getId(),
type: event.getWireType(),
sender: event.getSender(),
state_key: event.getStateKey(),
content: event.getWireContent(),
origin_server_ts: event.getTs(),
});
}
function rustEncryptionInfoToJsEncryptionInfo(
encryptionInfo: RustSdkCryptoJs.EncryptionInfo | undefined,
): EventEncryptionInfo | null {
if (encryptionInfo === undefined) {
// not decrypted here
return null;
}
// TODO: use strict shield semantics.
const shieldState = encryptionInfo.shieldState(false);
let shieldColour: EventShieldColour;
switch (shieldState.color) {
case RustSdkCryptoJs.ShieldColor.Grey:
shieldColour = EventShieldColour.GREY;
break;
case RustSdkCryptoJs.ShieldColor.None:
shieldColour = EventShieldColour.NONE;
break;
default:
shieldColour = EventShieldColour.RED;
}
let shieldReason: EventShieldReason | null;
if (shieldState.message === null) {
shieldReason = null;
} else if (shieldState.message === "Encrypted by an unverified user.") {
shieldReason = EventShieldReason.UNVERIFIED_IDENTITY;
} else if (shieldState.message === "Encrypted by a device not verified by its owner.") {
shieldReason = EventShieldReason.UNSIGNED_DEVICE;
} else if (
shieldState.message === "The authenticity of this encrypted message can't be guaranteed on this device."
) {
shieldReason = EventShieldReason.AUTHENTICITY_NOT_GUARANTEED;
} else if (shieldState.message === "Encrypted by an unknown or deleted device.") {
shieldReason = EventShieldReason.UNKNOWN_DEVICE;
} else {
logger.warn(`Unknown shield state message '${shieldState.message}'`);
shieldReason = EventShieldReason.UNKNOWN;
}
return { shieldColour, shieldReason };
}
type RustCryptoEvents = type RustCryptoEvents =
| CryptoEvent.VerificationRequestReceived | CryptoEvent.VerificationRequestReceived
| CryptoEvent.UserTrustStatusChanged | CryptoEvent.UserTrustStatusChanged