-
-
Notifications
You must be signed in to change notification settings - Fork 603
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Device Dehydration | js-sdk: store/load dehydration key #4599
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,8 +21,9 @@ | |
import { IHttpOpts, MatrixError, MatrixHttpApi, Method } from "../http-api/index.ts"; | ||
import { IToDeviceEvent } from "../sync-accumulator.ts"; | ||
import { ServerSideSecretStorage } from "../secret-storage.ts"; | ||
import { decodeBase64, encodeUnpaddedBase64 } from "../base64.ts"; | ||
import { decodeBase64 } from "../base64.ts"; | ||
import { Logger } from "../logger.ts"; | ||
import { DehydratedDevicesEvents, DehydratedDevicesAPI } from "../crypto-api/index.ts"; | ||
|
||
/** | ||
* The response body of `GET /_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device`. | ||
|
@@ -67,9 +68,7 @@ | |
* | ||
* @internal | ||
*/ | ||
export class DehydratedDeviceManager { | ||
/** the secret key used for dehydrating and rehydrating */ | ||
private key?: Uint8Array; | ||
export class DehydratedDeviceManager extends DehydratedDevicesAPI { | ||
/** the ID of the interval for periodically replacing the dehydrated device */ | ||
private intervalId?: ReturnType<typeof setInterval>; | ||
|
||
|
@@ -79,8 +78,18 @@ | |
private readonly http: MatrixHttpApi<IHttpOpts & { onlyData: true }>, | ||
private readonly outgoingRequestProcessor: OutgoingRequestProcessor, | ||
private readonly secretStorage: ServerSideSecretStorage, | ||
) {} | ||
) { | ||
super(); | ||
} | ||
|
||
private async getCachedKey(): Promise<RustSdkCryptoJs.DehydratedDeviceKey | undefined> { | ||
Check failure on line 85 in src/rust-crypto/DehydratedDeviceManager.ts GitHub Actions / Typescript Syntax Check
|
||
return await this.olmMachine.dehydratedDevices().getDehydratedDeviceKey(); | ||
Check failure on line 86 in src/rust-crypto/DehydratedDeviceManager.ts GitHub Actions / Typescript Syntax Check
Check failure on line 86 in src/rust-crypto/DehydratedDeviceManager.ts GitHub Actions / Jest [integ] (Node lts/*)Device dehydration › should rehydrate and dehydrate a device
Check failure on line 86 in src/rust-crypto/DehydratedDeviceManager.ts GitHub Actions / Jest [integ] (Node 22)Device dehydration › should rehydrate and dehydrate a device
|
||
} | ||
|
||
private async cacheKey(key: RustSdkCryptoJs.DehydratedDeviceKey): Promise<void> { | ||
Check failure on line 89 in src/rust-crypto/DehydratedDeviceManager.ts GitHub Actions / Typescript Syntax Check
|
||
await this.olmMachine.dehydratedDevices().saveDehydratedDeviceKey(key); | ||
this.emit(DehydratedDevicesEvents.PickleKeyCached); | ||
} | ||
/** | ||
* Return whether the server supports dehydrated devices. | ||
*/ | ||
|
@@ -153,10 +162,10 @@ | |
* Creates a new key and stores it in secret storage. | ||
*/ | ||
public async resetKey(): Promise<void> { | ||
const key = new Uint8Array(32); | ||
globalThis.crypto.getRandomValues(key); | ||
await this.secretStorage.store(SECRET_STORAGE_NAME, encodeUnpaddedBase64(key)); | ||
this.key = key; | ||
const key = RustSdkCryptoJs.DehydratedDeviceKey.createRandomKey(); | ||
Check failure on line 165 in src/rust-crypto/DehydratedDeviceManager.ts GitHub Actions / Typescript Syntax Check
|
||
await this.secretStorage.store(SECRET_STORAGE_NAME, key.toBase64()); | ||
// also cache it | ||
await this.cacheKey(key); | ||
} | ||
|
||
/** | ||
|
@@ -166,19 +175,22 @@ | |
* | ||
* @returns the key, if available, or `null` if no key is available | ||
*/ | ||
private async getKey(create: boolean): Promise<Uint8Array | null> { | ||
if (this.key === undefined) { | ||
private async getKey(create: boolean): Promise<RustSdkCryptoJs.DehydratedDeviceKey | null> { | ||
Check failure on line 178 in src/rust-crypto/DehydratedDeviceManager.ts GitHub Actions / Typescript Syntax Check
|
||
const cachedKey = await this.getCachedKey(); | ||
if (!cachedKey) { | ||
const keyB64 = await this.secretStorage.get(SECRET_STORAGE_NAME); | ||
if (keyB64 === undefined) { | ||
if (!create) { | ||
return null; | ||
} | ||
await this.resetKey(); | ||
} else { | ||
this.key = decodeBase64(keyB64); | ||
const bytes = decodeBase64(keyB64); | ||
const key = RustSdkCryptoJs.DehydratedDeviceKey.createKeyFromArray(bytes); | ||
Check failure on line 189 in src/rust-crypto/DehydratedDeviceManager.ts GitHub Actions / Typescript Syntax Check
|
||
await this.cacheKey(key); | ||
} | ||
} | ||
return this.key!; | ||
return (await this.getCachedKey())!; | ||
} | ||
|
||
/** | ||
|
@@ -190,7 +202,7 @@ | |
* Returns whether or not a dehydrated device was found. | ||
*/ | ||
public async rehydrateDeviceIfAvailable(): Promise<boolean> { | ||
const key = await this.getKey(false); | ||
const key = (await this.getCachedKey()) || (await this.getKey(false)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is |
||
if (!key) { | ||
return false; | ||
} | ||
|
@@ -219,6 +231,7 @@ | |
} | ||
|
||
this.logger.info("dehydration: dehydrated device found"); | ||
this.emit(DehydratedDevicesEvents.RehydrationStarted); | ||
|
||
const rehydratedDevice = await this.olmMachine | ||
.dehydratedDevices() | ||
|
@@ -255,8 +268,11 @@ | |
nextBatch = eventResp.next_batch; | ||
const roomKeyInfos = await rehydratedDevice.receiveEvents(JSON.stringify(eventResp.events)); | ||
roomKeyCount += roomKeyInfos.length; | ||
|
||
this.emit(DehydratedDevicesEvents.RehydrationProgress, roomKeyCount, toDeviceCount); | ||
} | ||
this.logger.info(`dehydration: received ${roomKeyCount} room keys from ${toDeviceCount} to-device events`); | ||
this.emit(DehydratedDevicesEvents.RehydrationEnded); | ||
|
||
return true; | ||
} | ||
|
@@ -267,12 +283,14 @@ | |
* Creates and stores a new key in secret storage if none is available. | ||
*/ | ||
public async createAndUploadDehydratedDevice(): Promise<void> { | ||
const key = (await this.getKey(true))!; | ||
const key = ((await this.getCachedKey()) || (await this.getKey(true)))!; | ||
|
||
const dehydratedDevice = await this.olmMachine.dehydratedDevices().create(); | ||
this.emit(DehydratedDevicesEvents.DeviceCreated); | ||
const request = await dehydratedDevice.keysForUpload("Dehydrated device", key); | ||
|
||
await this.outgoingRequestProcessor.makeOutgoingRequest(request); | ||
this.emit(DehydratedDevicesEvents.DeviceUploaded); | ||
|
||
this.logger.info("dehydration: uploaded device"); | ||
} | ||
|
@@ -287,6 +305,7 @@ | |
await this.createAndUploadDehydratedDevice(); | ||
this.intervalId = setInterval(() => { | ||
this.createAndUploadDehydratedDevice().catch((error) => { | ||
this.emit(DehydratedDevicesEvents.SchedulingError, error.message); | ||
this.logger.error("Error creating dehydrated device:", error); | ||
}); | ||
}, DEHYDRATION_INTERVAL); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bytes
should be zeroed out after this line, to avoid keeping the key in memory