diff --git a/spec/unit/rust-crypto/rust-crypto.spec.ts b/spec/unit/rust-crypto/rust-crypto.spec.ts index 9ba37a33b..8f44d0414 100644 --- a/spec/unit/rust-crypto/rust-crypto.spec.ts +++ b/spec/unit/rust-crypto/rust-crypto.spec.ts @@ -28,6 +28,7 @@ import { CryptoBackend } from "../../../src/common-crypto/CryptoBackend"; import { IEventDecryptionResult } from "../../../src/@types/crypto"; import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor"; import { ServerSideSecretStorage } from "../../../src/secret-storage"; +import { ImportRoomKeysOpts } from "../../../src/crypto-api"; afterEach(() => { // reset fake-indexeddb after each test, to make sure we don't leak connections @@ -40,16 +41,59 @@ const TEST_USER = "@alice:example.com"; const TEST_DEVICE_ID = "TEST_DEVICE"; describe("RustCrypto", () => { - describe(".exportRoomKeys", () => { + describe(".importRoomKeys and .exportRoomKeys", () => { let rustCrypto: RustCrypto; beforeEach(async () => { rustCrypto = await makeTestRustCrypto(); }); - it("should return a list", async () => { + it("should import and export keys", async () => { + const someRoomKeys = [ + { + algorithm: "m.megolm.v1.aes-sha2", + room_id: "!cLDYAnjpiQXIrSwngM:localhost:8480", + sender_key: "C9FMqTD20C0VaGWE/aSImkimuE6HDa/RyYj5gRUg3gY", + session_id: "iGQG5GaP1/B3dSH6zCQDQqrNuotrtQjVC7w1OsUDwbg", + session_key: + "AQAAAADaCbP2gdOy8jrhikjploKgSBaFSJ5rvHcziaADbwNEzeCSrfuAUlXvCvxik8kU+MfCHIi5arN2M7UM5rGKdzkHnkReoIByFkeMdbjKWk5SFpVQexcM74eDhBGj+ICkQqOgApfnEbSswrmreB0+MhHHyLStwW5fy5f8A9QW1sbPuohkBuRmj9fwd3Uh+swkA0KqzbqLa7UI1Qu8NTrFA8G4", + sender_claimed_keys: { + ed25519: "RSq0Xw0RR0DeqlJ/j3qrF5qbN0D96fKk8lz9kZJlG9k", + }, + forwarding_curve25519_key_chain: [], + }, + { + algorithm: "m.megolm.v1.aes-sha2", + room_id: "!cLDYAnjpiQXIrSwngM:localhost:8480", + sender_key: "C9FMqTD20C0VaGWE/aSImkimuE6HDa/RyYj5gRUg3gY", + session_id: "P/Jy9Tog4CMtLseeS4Fe2AEXZov3k6cibcop/uyhr78", + session_key: + "AQAAAAATyAVm0c9c9DW9Od72MxvfSDYoysBw3C6yMJ3bYuTmssHN7yNGm59KCtKeFp2Y5qO7lvUmwOfSTvTASUb7HViE7Lt+Bvp5WiMTJ2Pv6m+N12ihyowV5lgtKFWI18Wxd0AugMTVQRwjBK6aMobf86NXWD2hiKm3N6kWbC0PXmqV7T/ycvU6IOAjLS7HnkuBXtgBF2aL95OnIm3KKf7soa+/", + sender_claimed_keys: { + ed25519: "RSq0Xw0RR0DeqlJ/j3qrF5qbN0D96fKk8lz9kZJlG9k", + }, + forwarding_curve25519_key_chain: [], + }, + ]; + let importTotal = 0; + const opt: ImportRoomKeysOpts = { + progressCallback: (stage) => { + importTotal = stage.total; + }, + }; + await rustCrypto.importRoomKeys(someRoomKeys, opt); + + expect(importTotal).toBe(2); + const keys = await rustCrypto.exportRoomKeys(); expect(Array.isArray(keys)).toBeTruthy(); + expect(keys.length).toBe(2); + + const aSession = someRoomKeys[0]; + + const exportedKey = keys.find((k) => k.session_id == aSession.session_id); + + expect(aSession).toStrictEqual(exportedKey); }); }); diff --git a/src/client.ts b/src/client.ts index 44da2056b..bf7b5adfc 100644 --- a/src/client.ts +++ b/src/client.ts @@ -103,13 +103,7 @@ import { MatrixScheduler } from "./scheduler"; import { BeaconEvent, BeaconEventHandlerMap } from "./models/beacon"; import { IAuthData, IAuthDict } from "./interactive-auth"; import { IMinimalEvent, IRoomEvent, IStateEvent } from "./sync-accumulator"; -import { - CrossSigningKey, - ICreateSecretStorageOpts, - IEncryptedEventInfo, - IImportRoomKeysOpts, - IRecoveryKey, -} from "./crypto/api"; +import { CrossSigningKey, ICreateSecretStorageOpts, IEncryptedEventInfo, IRecoveryKey } from "./crypto/api"; import { EventTimelineSet } from "./models/event-timeline-set"; import { VerificationRequest } from "./crypto/verification/request/VerificationRequest"; import { VerificationBase as Verification } from "./crypto/verification/Base"; @@ -207,7 +201,7 @@ import { LocalNotificationSettings } from "./@types/local_notifications"; import { buildFeatureSupportMap, Feature, ServerSupport } from "./feature"; import { CryptoBackend } from "./common-crypto/CryptoBackend"; import { RUST_SDK_STORE_PREFIX } from "./rust-crypto/constants"; -import { BootstrapCrossSigningOpts, CryptoApi } from "./crypto-api"; +import { BootstrapCrossSigningOpts, CryptoApi, ImportRoomKeysOpts } from "./crypto-api"; import { DeviceInfoMap } from "./crypto/DeviceList"; import { AddSecretStorageKeyOpts, @@ -3195,14 +3189,20 @@ export class MatrixClient extends TypedEventEmitter { - if (!this.crypto) { + public importRoomKeys(keys: IMegolmSessionData[], opts?: ImportRoomKeysOpts): Promise { + if (!this.cryptoBackend) { throw new Error("End-to-end encryption disabled"); } - return this.crypto.importRoomKeys(keys, opts); + return this.cryptoBackend.importRoomKeys(keys, opts); } /** @@ -3810,7 +3810,7 @@ export class MatrixClient extends TypedEventEmitter; + /** + * Import a list of room keys previously exported by exportRoomKeys + * + * @param keys - a list of session export objects + * @param opts - options object + * @returns a promise which resolves once the keys have been imported + */ + importRoomKeys(keys: IMegolmSessionData[], opts?: ImportRoomKeysOpts): Promise; + /** * Get the device information for the given list of users. * @@ -269,6 +278,29 @@ export class DeviceVerificationStatus { } } +/** + * Room key import progress report. + * Used when calling {@link CryptoApi#importRoomKeys} as the parameter of + * the progressCallback. Used to display feedback. + */ +export interface ImportRoomKeyProgressData { + stage: string; // TODO: Enum + successes: number; + failures: number; + total: number; +} + +/** + * Options object for {@link CryptoApi#importRoomKeys}. + */ +export interface ImportRoomKeysOpts { + /** Reports ongoing progress of the import process. Can be used for feedback. */ + progressCallback?: (stage: ImportRoomKeyProgressData) => void; + // TODO, the rust SDK will always such imported keys as untrusted + untrusted?: boolean; + source?: String; // TODO: Enum (backup, file, ??) +} + export * from "./crypto-api/verification"; /** diff --git a/src/crypto/api.ts b/src/crypto/api.ts index 97ead425d..db9503300 100644 --- a/src/crypto/api.ts +++ b/src/crypto/api.ts @@ -20,6 +20,12 @@ import type { AddSecretStorageKeyOpts } from "../secret-storage"; /* re-exports for backwards compatibility. */ export { CrossSigningKey } from "../crypto-api"; + +export type { + ImportRoomKeyProgressData as IImportOpts, + ImportRoomKeysOpts as IImportRoomKeysOpts, +} from "../crypto-api"; + export type { AddSecretStorageKeyOpts as IAddSecretStorageKeyOpts, PassphraseInfo as IPassphraseInfo, @@ -100,17 +106,3 @@ export interface ICreateSecretStorageOpts { */ getKeyBackupPassphrase?: () => Promise; } - -export interface IImportOpts { - stage: string; // TODO: Enum - successes: number; - failures: number; - total: number; -} - -export interface IImportRoomKeysOpts { - /** called with an object that has a "stage" param */ - progressCallback?: (stage: IImportOpts) => void; - untrusted?: boolean; - source?: string; // TODO: Enum -} diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 17065b475..16462d744 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -35,13 +35,7 @@ import * as algorithms from "./algorithms"; import { createCryptoStoreCacheCallbacks, CrossSigningInfo, DeviceTrustLevel, UserTrustLevel } from "./CrossSigning"; import { EncryptionSetupBuilder } from "./EncryptionSetup"; import { SecretStorage as LegacySecretStorage } from "./SecretStorage"; -import { - CrossSigningKey, - ICreateSecretStorageOpts, - IEncryptedEventInfo, - IImportRoomKeysOpts, - IRecoveryKey, -} from "./api"; +import { CrossSigningKey, ICreateSecretStorageOpts, IEncryptedEventInfo, IRecoveryKey } from "./api"; import { OutgoingRoomKeyRequestManager } from "./OutgoingRoomKeyRequestManager"; import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store"; import { VerificationBase } from "./verification/Base"; @@ -93,7 +87,12 @@ import { ServerSideSecretStorageImpl, } from "../secret-storage"; import { ISecretRequest } from "./SecretSharing"; -import { BootstrapCrossSigningOpts, CrossSigningStatus, DeviceVerificationStatus } from "../crypto-api"; +import { + BootstrapCrossSigningOpts, + CrossSigningStatus, + DeviceVerificationStatus, + ImportRoomKeysOpts, +} from "../crypto-api"; import { Device, DeviceMap } from "../models/device"; import { deviceInfoToDevice } from "./device-converter"; @@ -2852,7 +2851,7 @@ export class Crypto extends TypedEventEmitter { + public importRoomKeys(keys: IMegolmSessionData[], opts: ImportRoomKeysOpts = {}): Promise { let successes = 0; let failures = 0; const total = keys.length; diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index 9ca76c6f6..b23c7fb46 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -30,7 +30,13 @@ import { RoomEncryptor } from "./RoomEncryptor"; import { OutgoingRequest, OutgoingRequestProcessor } from "./OutgoingRequestProcessor"; import { KeyClaimManager } from "./KeyClaimManager"; import { MapWithDefault } from "../utils"; -import { BootstrapCrossSigningOpts, CrossSigningStatus, DeviceVerificationStatus } from "../crypto-api"; +import { + BootstrapCrossSigningOpts, + CrossSigningStatus, + DeviceVerificationStatus, + ImportRoomKeyProgressData, + ImportRoomKeysOpts, +} from "../crypto-api"; import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter"; import { IDownloadKeyResult, IQueryKeysRequest } from "../client"; import { Device, DeviceMap } from "../models/device"; @@ -207,8 +213,22 @@ export class RustCrypto implements CryptoBackend { } public async exportRoomKeys(): Promise { - // TODO - return []; + const raw = await this.olmMachine.exportRoomKeys(() => true); + return JSON.parse(raw); + } + + public async importRoomKeys(keys: IMegolmSessionData[], opts?: ImportRoomKeysOpts): Promise { + // TODO when backup support will be added we would need to expose the `from_backup` flag in the bindings + const jsonKeys = JSON.stringify(keys); + await this.olmMachine.importRoomKeys(jsonKeys, (progress: BigInt, total: BigInt) => { + const importOpt: ImportRoomKeyProgressData = { + total: Number(total), + successes: Number(progress), + stage: "load_keys", + failures: 0, + }; + opts?.progressCallback?.(importOpt); + }); } /**