You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-31 15:24:23 +03:00
Groundwork for supporting migration from libolm to rust crypto. (#3977)
* `getOwnDeviceKeys`: use `olmMachine.identityKeys` This is simpler, and doesn't rely on us having done a device query to work. * Factor out `requestKeyBackupVersion` utility * Factor out `makeMatrixHttpApi` function * Convert `initRustCrypto` to take a params object * Improve logging in startup ... to help figure out what is taking so long.
This commit is contained in:
committed by
GitHub
parent
c115e055c6
commit
d030c83cee
@ -398,17 +398,11 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
||||
expect(aliceClient.getCrypto()).toHaveProperty("globalBlacklistUnverifiedDevices");
|
||||
});
|
||||
|
||||
it("CryptoAPI.getOwnedDeviceKeys returns the correct values", async () => {
|
||||
const homeserverUrl = aliceClient.getHomeserverUrl();
|
||||
|
||||
keyResponder = new E2EKeyResponder(homeserverUrl);
|
||||
await startClientAndAwaitFirstSync();
|
||||
keyResponder.addKeyReceiver("@alice:localhost", keyReceiver);
|
||||
|
||||
it("CryptoAPI.getOwnDeviceKeys returns plausible values", async () => {
|
||||
const deviceKeys = await aliceClient.getCrypto()!.getOwnDeviceKeys();
|
||||
|
||||
expect(deviceKeys.curve25519).toEqual(keyReceiver.getDeviceKey());
|
||||
expect(deviceKeys.ed25519).toEqual(keyReceiver.getSigningKey());
|
||||
// We just check for a 43-character base64 string
|
||||
expect(deviceKeys.curve25519).toMatch(/^[A-Za-z0-9+/]{43}$/);
|
||||
expect(deviceKeys.ed25519).toMatch(/^[A-Za-z0-9+/]{43}$/);
|
||||
});
|
||||
|
||||
it("Alice receives a megolm message", async () => {
|
||||
|
@ -84,16 +84,16 @@ describe("initRustCrypto", () => {
|
||||
const testOlmMachine = makeTestOlmMachine();
|
||||
jest.spyOn(OlmMachine, "initialize").mockResolvedValue(testOlmMachine);
|
||||
|
||||
await initRustCrypto(
|
||||
await initRustCrypto({
|
||||
logger,
|
||||
{} as MatrixClient["http"],
|
||||
TEST_USER,
|
||||
TEST_DEVICE_ID,
|
||||
{} as ServerSideSecretStorage,
|
||||
{} as CryptoCallbacks,
|
||||
"storePrefix",
|
||||
"storePassphrase",
|
||||
);
|
||||
http: {} as MatrixClient["http"],
|
||||
userId: TEST_USER,
|
||||
deviceId: TEST_DEVICE_ID,
|
||||
secretStorage: {} as ServerSideSecretStorage,
|
||||
cryptoCallbacks: {} as CryptoCallbacks,
|
||||
storePrefix: "storePrefix",
|
||||
storePassphrase: "storePassphrase",
|
||||
});
|
||||
|
||||
expect(OlmMachine.initialize).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
@ -107,16 +107,16 @@ describe("initRustCrypto", () => {
|
||||
const testOlmMachine = makeTestOlmMachine();
|
||||
jest.spyOn(OlmMachine, "initialize").mockResolvedValue(testOlmMachine);
|
||||
|
||||
await initRustCrypto(
|
||||
await initRustCrypto({
|
||||
logger,
|
||||
{} as MatrixClient["http"],
|
||||
TEST_USER,
|
||||
TEST_DEVICE_ID,
|
||||
{} as ServerSideSecretStorage,
|
||||
{} as CryptoCallbacks,
|
||||
null,
|
||||
"storePassphrase",
|
||||
);
|
||||
http: {} as MatrixClient["http"],
|
||||
userId: TEST_USER,
|
||||
deviceId: TEST_DEVICE_ID,
|
||||
secretStorage: {} as ServerSideSecretStorage,
|
||||
cryptoCallbacks: {} as CryptoCallbacks,
|
||||
storePrefix: null,
|
||||
storePassphrase: "storePassphrase",
|
||||
});
|
||||
|
||||
expect(OlmMachine.initialize).toHaveBeenCalledWith(expect.anything(), expect.anything(), undefined, undefined);
|
||||
});
|
||||
@ -125,16 +125,16 @@ describe("initRustCrypto", () => {
|
||||
const testOlmMachine = makeTestOlmMachine() as OlmMachine;
|
||||
jest.spyOn(OlmMachine, "initialize").mockResolvedValue(testOlmMachine);
|
||||
|
||||
await initRustCrypto(
|
||||
await initRustCrypto({
|
||||
logger,
|
||||
{} as MatrixClient["http"],
|
||||
TEST_USER,
|
||||
TEST_DEVICE_ID,
|
||||
{} as ServerSideSecretStorage,
|
||||
{} as CryptoCallbacks,
|
||||
"storePrefix",
|
||||
"storePassphrase",
|
||||
);
|
||||
http: {} as MatrixClient["http"],
|
||||
userId: TEST_USER,
|
||||
deviceId: TEST_DEVICE_ID,
|
||||
secretStorage: {} as ServerSideSecretStorage,
|
||||
cryptoCallbacks: {} as CryptoCallbacks,
|
||||
storePrefix: "storePrefix",
|
||||
storePassphrase: "storePassphrase",
|
||||
});
|
||||
|
||||
expect(testOlmMachine.getSecretsFromInbox).toHaveBeenCalledWith("m.megolm_backup.v1");
|
||||
});
|
||||
@ -823,11 +823,6 @@ describe("RustCrypto", () => {
|
||||
it("should wait for a keys/query before returning devices", async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const mockHttpApi = new MatrixHttpApi(new TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>(), {
|
||||
baseUrl: "http://server/",
|
||||
prefix: "",
|
||||
onlyData: true,
|
||||
});
|
||||
fetchMock.post("path:/_matrix/client/v3/keys/upload", { one_time_key_counts: {} });
|
||||
fetchMock.post("path:/_matrix/client/v3/keys/query", {
|
||||
device_keys: {
|
||||
@ -837,7 +832,7 @@ describe("RustCrypto", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const rustCrypto = await makeTestRustCrypto(mockHttpApi, testData.TEST_USER_ID);
|
||||
const rustCrypto = await makeTestRustCrypto(makeMatrixHttpApi(), testData.TEST_USER_ID);
|
||||
|
||||
// an attempt to fetch the device list should block
|
||||
const devicesPromise = rustCrypto.getUserDeviceInfo([testData.TEST_USER_ID]);
|
||||
@ -963,12 +958,6 @@ describe("RustCrypto", () => {
|
||||
// Return the key backup
|
||||
fetchMock.get("path:/_matrix/client/v3/room_keys/version", testData.SIGNED_BACKUP_DATA);
|
||||
|
||||
const mockHttpApi = new MatrixHttpApi(new TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>(), {
|
||||
baseUrl: "http://server/",
|
||||
prefix: "",
|
||||
onlyData: true,
|
||||
});
|
||||
|
||||
const olmMachine = {
|
||||
getIdentity: jest.fn(),
|
||||
// Force the backup to be trusted by the olmMachine
|
||||
@ -981,7 +970,7 @@ describe("RustCrypto", () => {
|
||||
const rustCrypto = new RustCrypto(
|
||||
logger,
|
||||
olmMachine,
|
||||
mockHttpApi,
|
||||
makeMatrixHttpApi(),
|
||||
testData.TEST_USER_ID,
|
||||
testData.TEST_DEVICE_ID,
|
||||
{} as ServerSideSecretStorage,
|
||||
@ -1040,6 +1029,15 @@ describe("RustCrypto", () => {
|
||||
});
|
||||
});
|
||||
|
||||
/** Build a MatrixHttpApi instance */
|
||||
function makeMatrixHttpApi(): MatrixHttpApi<IHttpOpts & { onlyData: true }> {
|
||||
return new MatrixHttpApi(new TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>(), {
|
||||
baseUrl: "http://server/",
|
||||
prefix: "",
|
||||
onlyData: true,
|
||||
});
|
||||
}
|
||||
|
||||
/** build a basic RustCrypto instance for testing
|
||||
*
|
||||
* just provides default arguments for initRustCrypto()
|
||||
@ -1051,7 +1049,16 @@ async function makeTestRustCrypto(
|
||||
secretStorage: ServerSideSecretStorage = {} as ServerSideSecretStorage,
|
||||
cryptoCallbacks: CryptoCallbacks = {} as CryptoCallbacks,
|
||||
): Promise<RustCrypto> {
|
||||
return await initRustCrypto(logger, http, userId, deviceId, secretStorage, cryptoCallbacks, null, undefined);
|
||||
return await initRustCrypto({
|
||||
logger,
|
||||
http,
|
||||
userId,
|
||||
deviceId,
|
||||
secretStorage,
|
||||
cryptoCallbacks,
|
||||
storePrefix: null,
|
||||
storePassphrase: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/** emulate account data, storing in memory
|
||||
|
@ -2316,17 +2316,19 @@ 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");
|
||||
const RustCrypto = await import("./rust-crypto");
|
||||
const rustCrypto = await RustCrypto.initRustCrypto(
|
||||
this.logger,
|
||||
this.http,
|
||||
userId,
|
||||
deviceId,
|
||||
this.secretStorage,
|
||||
this.cryptoCallbacks,
|
||||
useIndexedDB ? RUST_SDK_STORE_PREFIX : null,
|
||||
this.pickleKey,
|
||||
);
|
||||
|
||||
const rustCrypto = await RustCrypto.initRustCrypto({
|
||||
logger: this.logger,
|
||||
http: this.http,
|
||||
userId: userId,
|
||||
deviceId: deviceId,
|
||||
secretStorage: this.secretStorage,
|
||||
cryptoCallbacks: this.cryptoCallbacks,
|
||||
storePrefix: useIndexedDB ? RUST_SDK_STORE_PREFIX : null,
|
||||
storePassphrase: this.pickleKey,
|
||||
});
|
||||
rustCrypto.setSupportedVerificationMethods(this.verificationMethods);
|
||||
|
||||
this.cryptoBackend = rustCrypto;
|
||||
|
@ -35,6 +35,7 @@ import { sleep } from "../utils";
|
||||
import { BackupDecryptor } from "../common-crypto/CryptoBackend";
|
||||
import { IEncryptedPayload } from "../crypto/aes";
|
||||
import { ImportRoomKeyProgressData, ImportRoomKeysOpts } from "../crypto-api";
|
||||
import { IKeyBackupInfo } from "../crypto/keybackup";
|
||||
|
||||
/** Authentification of the backup info, depends on algorithm */
|
||||
type AuthData = KeyBackupInfo["auth_data"];
|
||||
@ -399,23 +400,7 @@ export class RustBackupManager extends TypedEventEmitter<RustBackupCryptoEvents,
|
||||
* @returns Information object from API or null if there is no active backup.
|
||||
*/
|
||||
public async requestKeyBackupVersion(): Promise<KeyBackupInfo | null> {
|
||||
try {
|
||||
return await this.http.authedRequest<KeyBackupInfo>(
|
||||
Method.Get,
|
||||
"/room_keys/version",
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
prefix: ClientPrefix.V3,
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
if ((<MatrixError>e).errcode === "M_NOT_FOUND") {
|
||||
return null;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return await requestKeyBackupVersion(this.http);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -567,6 +552,22 @@ export class RustBackupDecryptor implements BackupDecryptor {
|
||||
}
|
||||
}
|
||||
|
||||
export async function requestKeyBackupVersion(
|
||||
http: MatrixHttpApi<IHttpOpts & { onlyData: true }>,
|
||||
): Promise<IKeyBackupInfo | null> {
|
||||
try {
|
||||
return await http.authedRequest<KeyBackupInfo>(Method.Get, "/room_keys/version", undefined, undefined, {
|
||||
prefix: ClientPrefix.V3,
|
||||
});
|
||||
} catch (e) {
|
||||
if ((<MatrixError>e).errcode === "M_NOT_FOUND") {
|
||||
return null;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type RustBackupCryptoEvents =
|
||||
| CryptoEvent.KeyBackupStatus
|
||||
| CryptoEvent.KeyBackupSessionsRemaining
|
||||
|
@ -25,22 +25,70 @@ import { Logger } from "../logger";
|
||||
/**
|
||||
* Create a new `RustCrypto` implementation
|
||||
*
|
||||
* @param logger - A `Logger` instance that will be used for debug output.
|
||||
* @param http - Low-level HTTP interface: used to make outgoing requests required by the rust SDK.
|
||||
* We expect it to set the access token, etc.
|
||||
* @param userId - The local user's User ID.
|
||||
* @param deviceId - The local user's Device ID.
|
||||
* @param secretStorage - Interface to server-side secret storage.
|
||||
* @param cryptoCallbacks - Crypto callbacks provided by the application
|
||||
* @param storePrefix - the prefix to use on the indexeddbs created by rust-crypto.
|
||||
* If `null`, a memory store will be used.
|
||||
* @param storePassphrase - a passphrase to use to encrypt the indexeddbs created by rust-crypto.
|
||||
* Ignored if `storePrefix` is null. If this is `undefined` (and `storePrefix` is not null), the indexeddbs
|
||||
* will be unencrypted.
|
||||
*
|
||||
* @param args - Parameter object
|
||||
* @internal
|
||||
*/
|
||||
export async function initRustCrypto(
|
||||
export async function initRustCrypto(args: {
|
||||
/** A `Logger` instance that will be used for debug output. */
|
||||
logger: Logger;
|
||||
|
||||
/**
|
||||
* Low-level HTTP interface: used to make outgoing requests required by the rust SDK.
|
||||
* We expect it to set the access token, etc.
|
||||
*/
|
||||
http: MatrixHttpApi<IHttpOpts & { onlyData: true }>;
|
||||
|
||||
/** The local user's User ID. */
|
||||
userId: string;
|
||||
|
||||
/** The local user's Device ID. */
|
||||
deviceId: string;
|
||||
|
||||
/** Interface to server-side secret storage. */
|
||||
secretStorage: ServerSideSecretStorage;
|
||||
|
||||
/** Crypto callbacks provided by the application. */
|
||||
cryptoCallbacks: ICryptoCallbacks;
|
||||
|
||||
/**
|
||||
* The prefix to use on the indexeddbs created by rust-crypto.
|
||||
* If `null`, a memory store will be used.
|
||||
*/
|
||||
storePrefix: string | null;
|
||||
|
||||
/**
|
||||
* A passphrase to use to encrypt the indexeddbs created by rust-crypto.
|
||||
*
|
||||
* Ignored if `storePrefix` is null. If this is `undefined` (and `storePrefix` is not null), the indexeddbs
|
||||
* will be unencrypted.
|
||||
*/
|
||||
storePassphrase?: string;
|
||||
}): Promise<RustCrypto> {
|
||||
const { logger } = args;
|
||||
|
||||
// initialise the rust matrix-sdk-crypto-wasm, if it hasn't already been done
|
||||
logger.debug("Initialising Rust crypto-sdk WASM artifact");
|
||||
await RustSdkCryptoJs.initAsync();
|
||||
|
||||
// enable tracing in the rust-sdk
|
||||
new RustSdkCryptoJs.Tracing(RustSdkCryptoJs.LoggerLevel.Debug).turnOn();
|
||||
|
||||
const rustCrypto = await initOlmMachine(
|
||||
logger,
|
||||
args.http,
|
||||
args.userId,
|
||||
args.deviceId,
|
||||
args.secretStorage,
|
||||
args.cryptoCallbacks,
|
||||
args.storePrefix,
|
||||
args.storePassphrase,
|
||||
);
|
||||
|
||||
logger.debug("Completed rust crypto-sdk setup");
|
||||
return rustCrypto;
|
||||
}
|
||||
|
||||
async function initOlmMachine(
|
||||
logger: Logger,
|
||||
http: MatrixHttpApi<IHttpOpts & { onlyData: true }>,
|
||||
userId: string,
|
||||
@ -50,20 +98,10 @@ export async function initRustCrypto(
|
||||
storePrefix: string | null,
|
||||
storePassphrase: string | undefined,
|
||||
): Promise<RustCrypto> {
|
||||
// initialise the rust matrix-sdk-crypto-wasm, if it hasn't already been done
|
||||
await RustSdkCryptoJs.initAsync();
|
||||
|
||||
// enable tracing in the rust-sdk
|
||||
new RustSdkCryptoJs.Tracing(RustSdkCryptoJs.LoggerLevel.Debug).turnOn();
|
||||
|
||||
const u = new RustSdkCryptoJs.UserId(userId);
|
||||
const d = new RustSdkCryptoJs.DeviceId(deviceId);
|
||||
logger.info("Init OlmMachine");
|
||||
|
||||
// TODO: use the pickle key for the passphrase
|
||||
logger.debug("Init OlmMachine");
|
||||
const olmMachine = await RustSdkCryptoJs.OlmMachine.initialize(
|
||||
u,
|
||||
d,
|
||||
new RustSdkCryptoJs.UserId(userId),
|
||||
new RustSdkCryptoJs.DeviceId(deviceId),
|
||||
storePrefix ?? undefined,
|
||||
(storePrefix && storePassphrase) ?? undefined,
|
||||
);
|
||||
@ -101,6 +139,5 @@ export async function initRustCrypto(
|
||||
// XXX: find a less hacky way to do this.
|
||||
await olmMachine.outgoingRequests();
|
||||
|
||||
logger.info("Completed rust crypto-sdk setup");
|
||||
return rustCrypto;
|
||||
}
|
||||
|
@ -315,18 +315,8 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
|
||||
* Implementation of {@link CryptoApi#getOwnDeviceKeys}.
|
||||
*/
|
||||
public async getOwnDeviceKeys(): Promise<OwnDeviceKeys> {
|
||||
const device: RustSdkCryptoJs.Device = await this.olmMachine.getDevice(
|
||||
this.olmMachine.userId,
|
||||
this.olmMachine.deviceId,
|
||||
);
|
||||
// could be undefined if there is no such algorithm for that device.
|
||||
if (device.curve25519Key && device.ed25519Key) {
|
||||
return {
|
||||
ed25519: device.ed25519Key.toBase64(),
|
||||
curve25519: device.curve25519Key.toBase64(),
|
||||
};
|
||||
}
|
||||
throw new Error("Device keys not found");
|
||||
const keys = this.olmMachine.identityKeys;
|
||||
return { ed25519: keys.ed25519.toBase64(), curve25519: keys.curve25519.toBase64() };
|
||||
}
|
||||
|
||||
public prepareToEncrypt(room: Room): void {
|
||||
|
Reference in New Issue
Block a user