You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-25 05:23:13 +03:00
Implement getEncryptionInfoForEvent and deprecate getEventEncryptionInfo (#3693)
* Implement `getEncryptionInfoForEvent` and deprecate `getEventEncryptionInfo` * fix tsdoc * fix tests * Improve test coverage
This commit is contained in:
committed by
GitHub
parent
0700e86f58
commit
7e691bf700
@@ -15,11 +15,16 @@ import { sleep } from "../../src/utils";
|
||||
import { CRYPTO_ENABLED } from "../../src/client";
|
||||
import { DeviceInfo } from "../../src/crypto/deviceinfo";
|
||||
import { logger } from "../../src/logger";
|
||||
import { MemoryStore } from "../../src";
|
||||
import { DeviceVerification, MemoryStore } from "../../src";
|
||||
import { RoomKeyRequestState } from "../../src/crypto/OutgoingRoomKeyRequestManager";
|
||||
import { RoomMember } from "../../src/models/room-member";
|
||||
import { IStore } from "../../src/store";
|
||||
import { IRoomEncryption, RoomList } from "../../src/crypto/RoomList";
|
||||
import { EventShieldColour, EventShieldReason } from "../../src/crypto-api";
|
||||
import { UserTrustLevel } from "../../src/crypto/CrossSigning";
|
||||
import { CryptoBackend } from "../../src/common-crypto/CryptoBackend";
|
||||
import { EventDecryptionResult } from "../../src/common-crypto/CryptoBackend";
|
||||
import * as testData from "../test-utils/test-data";
|
||||
|
||||
const Olm = global.Olm;
|
||||
|
||||
@@ -111,13 +116,14 @@ describe("Crypto", function () {
|
||||
});
|
||||
|
||||
describe("encrypted events", function () {
|
||||
it("provides encryption information", async function () {
|
||||
it("provides encryption information for events from unverified senders", async function () {
|
||||
const client = new TestClient("@alice:example.com", "deviceid").client;
|
||||
await client.initCrypto();
|
||||
|
||||
// unencrypted event
|
||||
const event = {
|
||||
getId: () => "$event_id",
|
||||
getSender: () => "@bob:example.com",
|
||||
getSenderKey: () => null,
|
||||
getWireContent: () => {
|
||||
return {};
|
||||
@@ -127,6 +133,8 @@ describe("Crypto", function () {
|
||||
let encryptionInfo = client.getEventEncryptionInfo(event);
|
||||
expect(encryptionInfo.encrypted).toBeFalsy();
|
||||
|
||||
expect(await client.getCrypto()!.getEncryptionInfoForEvent(event)).toBe(null);
|
||||
|
||||
// unknown sender (e.g. deleted device), forwarded megolm key (untrusted)
|
||||
event.getSenderKey = () => "YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI";
|
||||
event.getWireContent = () => {
|
||||
@@ -141,6 +149,11 @@ describe("Crypto", function () {
|
||||
expect(encryptionInfo.authenticated).toBeFalsy();
|
||||
expect(encryptionInfo.sender).toBeFalsy();
|
||||
|
||||
expect(await client.getCrypto()!.getEncryptionInfoForEvent(event)).toEqual({
|
||||
shieldColour: EventShieldColour.GREY,
|
||||
shieldReason: EventShieldReason.AUTHENTICITY_NOT_GUARANTEED,
|
||||
});
|
||||
|
||||
// known sender, megolm key from backup
|
||||
event.getForwardingCurve25519KeyChain = () => [];
|
||||
event.isKeySourceUntrusted = () => true;
|
||||
@@ -155,6 +168,11 @@ describe("Crypto", function () {
|
||||
expect(encryptionInfo.sender).toBeTruthy();
|
||||
expect(encryptionInfo.mismatchedSender).toBeFalsy();
|
||||
|
||||
expect(await client.getCrypto()!.getEncryptionInfoForEvent(event)).toEqual({
|
||||
shieldColour: EventShieldColour.GREY,
|
||||
shieldReason: EventShieldReason.AUTHENTICITY_NOT_GUARANTEED,
|
||||
});
|
||||
|
||||
// known sender, trusted megolm key, but bad ed25519key
|
||||
event.isKeySourceUntrusted = () => false;
|
||||
device.keys["ed25519:FLIBBLE"] = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
|
||||
@@ -165,9 +183,115 @@ describe("Crypto", function () {
|
||||
expect(encryptionInfo.sender).toBeTruthy();
|
||||
expect(encryptionInfo.mismatchedSender).toBeTruthy();
|
||||
|
||||
expect(await client.getCrypto()!.getEncryptionInfoForEvent(event)).toEqual({
|
||||
shieldColour: EventShieldColour.RED,
|
||||
shieldReason: EventShieldReason.MISMATCHED_SENDER_KEY,
|
||||
});
|
||||
|
||||
client.stopClient();
|
||||
});
|
||||
|
||||
describe("provides encryption information for events from verified senders", function () {
|
||||
const testDeviceId = testData.BOB_TEST_DEVICE_ID;
|
||||
const testDevice = testData.BOB_SIGNED_TEST_DEVICE_DATA;
|
||||
|
||||
let client: MatrixClient;
|
||||
beforeEach(async () => {
|
||||
client = new TestClient("@alice:example.com", "deviceid").client;
|
||||
await client.initCrypto();
|
||||
|
||||
// mock out the verification check
|
||||
client.crypto!.checkUserTrust = (userId) => new UserTrustLevel(true, false, false);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
client.stopClient();
|
||||
});
|
||||
|
||||
async function buildEncryptedEvent(
|
||||
decryptionResult: Partial<EventDecryptionResult> = {},
|
||||
): Promise<MatrixEvent> {
|
||||
const mockCryptoBackend = {
|
||||
decryptEvent: async (event: MatrixEvent): Promise<EventDecryptionResult> => {
|
||||
return {
|
||||
claimedEd25519Key: testDevice.keys["ed25519:" + testDeviceId],
|
||||
clearEvent: {
|
||||
room_id: "!room_id",
|
||||
type: "m.room.message",
|
||||
content: { body: "test" },
|
||||
},
|
||||
forwardingCurve25519KeyChain: [],
|
||||
senderCurve25519Key: testDevice.keys["curve25519:" + testDeviceId],
|
||||
...decryptionResult,
|
||||
};
|
||||
},
|
||||
} as unknown as CryptoBackend;
|
||||
|
||||
const event = new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
sender: testData.BOB_TEST_USER_ID,
|
||||
type: "m.room.encrypted",
|
||||
content: { algorithm: "m.megolm.v1.aes-sha2" },
|
||||
});
|
||||
await event.attemptDecryption(mockCryptoBackend);
|
||||
return event;
|
||||
}
|
||||
|
||||
it("unknown device", async () => {
|
||||
const event = await buildEncryptedEvent();
|
||||
expect(await client.getCrypto()!.getEncryptionInfoForEvent(event)).toEqual({
|
||||
shieldColour: EventShieldColour.GREY,
|
||||
shieldReason: EventShieldReason.UNKNOWN_DEVICE,
|
||||
});
|
||||
});
|
||||
|
||||
it("known but unsigned device", async () => {
|
||||
client.crypto!.deviceList.storeDevicesForUser(testData.BOB_TEST_USER_ID, {
|
||||
[testDeviceId]: {
|
||||
keys: testDevice.keys,
|
||||
algorithms: testDevice.algorithms,
|
||||
verified: DeviceVerification.Unverified,
|
||||
known: true,
|
||||
},
|
||||
});
|
||||
|
||||
const event = await buildEncryptedEvent();
|
||||
expect(await client.getCrypto()!.getEncryptionInfoForEvent(event)).toEqual({
|
||||
shieldColour: EventShieldColour.RED,
|
||||
shieldReason: EventShieldReason.UNVERIFIED_IDENTITY,
|
||||
});
|
||||
});
|
||||
|
||||
describe("known and verified device", () => {
|
||||
beforeEach(() => {
|
||||
client.crypto!.deviceList.storeDevicesForUser(testData.BOB_TEST_USER_ID, {
|
||||
[testDeviceId]: {
|
||||
keys: testDevice.keys,
|
||||
algorithms: testDevice.algorithms,
|
||||
verified: DeviceVerification.Verified,
|
||||
known: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("regular key", async () => {
|
||||
const event = await buildEncryptedEvent();
|
||||
expect(await client.getCrypto()!.getEncryptionInfoForEvent(event)).toEqual({
|
||||
shieldColour: EventShieldColour.NONE,
|
||||
shieldReason: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("unauthenticated key", async () => {
|
||||
const event = await buildEncryptedEvent({ untrusted: true });
|
||||
expect(await client.getCrypto()!.getEncryptionInfoForEvent(event)).toEqual({
|
||||
shieldColour: EventShieldColour.GREY,
|
||||
shieldReason: EventShieldReason.AUTHENTICITY_NOT_GUARANTEED,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't throw an error when attempting to decrypt a redacted event", async () => {
|
||||
const client = new TestClient("@alice:example.com", "deviceid").client;
|
||||
await client.initCrypto();
|
||||
|
||||
@@ -2848,6 +2848,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* @param event - event to be checked
|
||||
* @returns The event information.
|
||||
* @deprecated Prefer {@link CryptoApi.getEncryptionInfoForEvent | `CryptoApi.getEncryptionInfoForEvent`}.
|
||||
*/
|
||||
public getEventEncryptionInfo(event: MatrixEvent): IEncryptedEventInfo {
|
||||
if (!this.cryptoBackend) {
|
||||
|
||||
@@ -203,6 +203,10 @@ export interface EventDecryptionResult {
|
||||
* ed25519 key claimed by the sender of this event. See {@link MatrixEvent#getClaimedEd25519Key}.
|
||||
*/
|
||||
claimedEd25519Key?: string;
|
||||
/**
|
||||
* Whether the keys for this event have been received via an unauthenticated source (eg via key forwards, or
|
||||
* restored from backup)
|
||||
*/
|
||||
untrusted?: boolean;
|
||||
/**
|
||||
* The sender doesn't authorize the unverified devices to decrypt his messages
|
||||
|
||||
@@ -22,6 +22,7 @@ import { AddSecretStorageKeyOpts, SecretStorageCallbacks, SecretStorageKeyDescri
|
||||
import { VerificationRequest } from "./crypto-api/verification";
|
||||
import { BackupTrustInfo, KeyBackupCheck, KeyBackupInfo } from "./crypto-api/keybackup";
|
||||
import { ISignatures } from "./@types/signed";
|
||||
import { MatrixEvent } from "./models/event";
|
||||
|
||||
/**
|
||||
* Public interface to the cryptography parts of the js-sdk
|
||||
@@ -265,6 +266,16 @@ export interface CryptoApi {
|
||||
*/
|
||||
createRecoveryKeyFromPassphrase(password?: string): Promise<GeneratedSecretStorageKey>;
|
||||
|
||||
/**
|
||||
* Get information about the encryption of the given event.
|
||||
*
|
||||
* @param event - the event to get information for
|
||||
*
|
||||
* @returns `null` if the event is not encrypted, or has not (yet) been successfully decrypted. Otherwise, an
|
||||
* object with information about the encryption of the event.
|
||||
*/
|
||||
getEncryptionInfoForEvent(event: MatrixEvent): Promise<EventEncryptionInfo | null>;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Device/User verification
|
||||
@@ -665,5 +676,57 @@ export interface GeneratedSecretStorageKey {
|
||||
encodedPrivateKey?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result type of {@link CryptoApi#getEncryptionInfoForEvent}.
|
||||
*/
|
||||
export interface EventEncryptionInfo {
|
||||
/** "Shield" to be shown next to this event representing its verification status */
|
||||
shieldColour: EventShieldColour;
|
||||
|
||||
/**
|
||||
* `null` if `shieldColour` is `EventShieldColour.NONE`; otherwise a reason code for the shield in `shieldColour`.
|
||||
*/
|
||||
shieldReason: EventShieldReason | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Types of shield to be shown for {@link EventEncryptionInfo#shieldColour}.
|
||||
*/
|
||||
export enum EventShieldColour {
|
||||
NONE,
|
||||
GREY,
|
||||
RED,
|
||||
}
|
||||
|
||||
/**
|
||||
* Reason codes for {@link EventEncryptionInfo#shieldReason}.
|
||||
*/
|
||||
export enum EventShieldReason {
|
||||
/** An unknown reason from the crypto library (if you see this, it is a bug in matrix-js-sdk). */
|
||||
UNKNOWN,
|
||||
|
||||
/** "Encrypted by an unverified user." */
|
||||
UNVERIFIED_IDENTITY,
|
||||
|
||||
/** "Encrypted by a device not verified by its owner." */
|
||||
UNSIGNED_DEVICE,
|
||||
|
||||
/** "Encrypted by an unknown or deleted device." */
|
||||
UNKNOWN_DEVICE,
|
||||
|
||||
/**
|
||||
* "The authenticity of this encrypted message can't be guaranteed on this device."
|
||||
*
|
||||
* ie: the key has been forwarded, or retrieved from an insecure backup.
|
||||
*/
|
||||
AUTHENTICITY_NOT_GUARANTEED,
|
||||
|
||||
/**
|
||||
* The (deprecated) sender_key field in the event does not match the Ed25519 key of the device that sent us the
|
||||
* decryption keys.
|
||||
*/
|
||||
MISMATCHED_SENDER_KEY,
|
||||
}
|
||||
|
||||
export * from "./crypto-api/verification";
|
||||
export * from "./crypto-api/keybackup";
|
||||
|
||||
@@ -91,6 +91,9 @@ import {
|
||||
BootstrapCrossSigningOpts,
|
||||
CrossSigningStatus,
|
||||
DeviceVerificationStatus,
|
||||
EventEncryptionInfo,
|
||||
EventShieldColour,
|
||||
EventShieldReason,
|
||||
ImportRoomKeysOpts,
|
||||
KeyBackupCheck,
|
||||
KeyBackupInfo,
|
||||
@@ -2701,6 +2704,68 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
return ret as IEncryptedEventInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of {@link CryptoApi.getEncryptionInfoForEvent}.
|
||||
*/
|
||||
public async getEncryptionInfoForEvent(event: MatrixEvent): Promise<EventEncryptionInfo | null> {
|
||||
const encryptionInfo = this.getEventEncryptionInfo(event);
|
||||
if (!encryptionInfo.encrypted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const senderId = event.getSender();
|
||||
if (!senderId || encryptionInfo.mismatchedSender) {
|
||||
// something definitely wrong is going on here
|
||||
return {
|
||||
shieldColour: EventShieldColour.RED,
|
||||
shieldReason: EventShieldReason.MISMATCHED_SENDER_KEY,
|
||||
};
|
||||
}
|
||||
|
||||
const userTrust = this.checkUserTrust(senderId);
|
||||
if (!userTrust.isCrossSigningVerified()) {
|
||||
// If the message is unauthenticated, then display a grey
|
||||
// shield, otherwise if the user isn't cross-signed then
|
||||
// nothing's needed
|
||||
if (!encryptionInfo.authenticated) {
|
||||
return {
|
||||
shieldColour: EventShieldColour.GREY,
|
||||
shieldReason: EventShieldReason.AUTHENTICITY_NOT_GUARANTEED,
|
||||
};
|
||||
} else {
|
||||
return { shieldColour: EventShieldColour.NONE, shieldReason: null };
|
||||
}
|
||||
}
|
||||
|
||||
const eventSenderTrust =
|
||||
senderId &&
|
||||
encryptionInfo.sender &&
|
||||
(await this.getDeviceVerificationStatus(senderId, encryptionInfo.sender.deviceId));
|
||||
|
||||
if (!eventSenderTrust) {
|
||||
return {
|
||||
shieldColour: EventShieldColour.GREY,
|
||||
shieldReason: EventShieldReason.UNKNOWN_DEVICE,
|
||||
};
|
||||
}
|
||||
|
||||
if (!eventSenderTrust.isVerified()) {
|
||||
return {
|
||||
shieldColour: EventShieldColour.RED,
|
||||
shieldReason: EventShieldReason.UNVERIFIED_IDENTITY,
|
||||
};
|
||||
}
|
||||
|
||||
if (!encryptionInfo.authenticated) {
|
||||
return {
|
||||
shieldColour: EventShieldColour.GREY,
|
||||
shieldReason: EventShieldReason.AUTHENTICITY_NOT_GUARANTEED,
|
||||
};
|
||||
}
|
||||
|
||||
return { shieldColour: EventShieldColour.NONE, shieldReason: null };
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the current outbound group session to be discarded such
|
||||
* that another one will be created next time an event is sent.
|
||||
|
||||
@@ -1026,7 +1026,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
||||
* signing the public curve25519 key with the ed25519 key.
|
||||
*
|
||||
* In general, applications should not use this method directly, but should
|
||||
* instead use MatrixClient.getEventSenderDeviceInfo.
|
||||
* instead use {@link CryptoApi#getEncryptionInfoForEvent}.
|
||||
*/
|
||||
public getClaimedEd25519Key(): string | null {
|
||||
return this.claimedEd25519Key;
|
||||
|
||||
@@ -39,6 +39,8 @@ import {
|
||||
CrossSigningStatus,
|
||||
CryptoCallbacks,
|
||||
DeviceVerificationStatus,
|
||||
EventEncryptionInfo,
|
||||
EventShieldColour,
|
||||
GeneratedSecretStorageKey,
|
||||
ImportRoomKeyProgressData,
|
||||
ImportRoomKeysOpts,
|
||||
@@ -208,6 +210,11 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
|
||||
return await this.eventDecryptor.attemptEventDecryption(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of (deprecated) {@link MatrixClient#getEventEncryptionInfo}.
|
||||
*
|
||||
* @param event - event to inspect
|
||||
*/
|
||||
public getEventEncryptionInfo(event: MatrixEvent): IEncryptedEventInfo {
|
||||
// TODO: make this work properly. Or better, replace it.
|
||||
|
||||
@@ -732,6 +739,16 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of {@link CryptoApi.getEncryptionInfoForEvent}.
|
||||
*/
|
||||
public async getEncryptionInfoForEvent(event: MatrixEvent): Promise<EventEncryptionInfo | null> {
|
||||
return {
|
||||
shieldColour: EventShieldColour.NONE,
|
||||
shieldReason: null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns to-device verification requests that are already in progress for the given user id.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user