-
-
Notifications
You must be signed in to change notification settings - Fork 623
Add support for device dehydration v2 (Element R) #4062
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
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
b855200
initial implementation of device dehydration
uhoreg 4c6254e
Merge branch 'develop' into dehydration_v2
uhoreg 8e632f3
add dehydrated flag for devices
uhoreg 4f231a6
add missing dehydration.ts file, add test, add function to schedule d…
uhoreg 560511d
add more dehydration utility functions
uhoreg 1def99a
stop scheduled dehydration when crypto stops
uhoreg 71eea91
Merge branch 'develop' into dehydration_v2
uhoreg 4fe5a64
bump matrix-crypto-sdk-wasm version, and fix tests
uhoreg d6fdce3
Merge branch 'develop' into dehydration_v2
uhoreg 12a934c
adding dehydratedDevices member to mock OlmDevice isn't necessary any…
uhoreg 937fac2
fix yarn lock file
uhoreg 49dedf2
more tests
uhoreg b0f0703
Merge branch 'develop' into dehydration_v2
uhoreg 7aa13df
fix test
uhoreg 3aa06dd
more tests
uhoreg 7c1e82e
fix typo
uhoreg 663448b
fix logic for checking if dehydration supported
uhoreg d870915
make changes from review
uhoreg 964333a
add missing file
uhoreg 3a66418
move setup into another function
uhoreg 0947a8a
apply changes from review
uhoreg f666788
implement simpler API
uhoreg 15c8c5c
Merge branch 'develop' into dehydration_v2
uhoreg 27784d7
fix type and move the code to the right spot
uhoreg 9ad28bb
apply suggestions from review
uhoreg 1525a16
make sure that cross-signing and secret storage are set up
uhoreg a995c46
Merge branch 'develop' into dehydration_v2
uhoreg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
/* | ||
Copyright 2024 The Matrix.org Foundation C.I.C. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
import "fake-indexeddb/auto"; | ||
import fetchMock from "fetch-mock-jest"; | ||
|
||
import { createClient, ClientEvent, MatrixClient, MatrixEvent } from "../../../src"; | ||
import { RustCrypto } from "../../../src/rust-crypto/rust-crypto"; | ||
import { AddSecretStorageKeyOpts } from "../../../src/secret-storage"; | ||
import { E2EKeyReceiver } from "../../test-utils/E2EKeyReceiver"; | ||
import { E2EKeyResponder } from "../../test-utils/E2EKeyResponder"; | ||
|
||
describe("Device dehydration", () => { | ||
it("should rehydrate and dehydrate a device", async () => { | ||
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] }); | ||
|
||
const matrixClient = createClient({ | ||
baseUrl: "http://test.server", | ||
userId: "@alice:localhost", | ||
deviceId: "aliceDevice", | ||
cryptoCallbacks: { | ||
getSecretStorageKey: async (keys: any, name: string) => { | ||
return [[...Object.keys(keys.keys)][0], new Uint8Array(32)]; | ||
}, | ||
}, | ||
}); | ||
|
||
await initializeSecretStorage(matrixClient, "@alice:localhost", "http://test.server"); | ||
|
||
// count the number of times the dehydration key gets set | ||
let setDehydrationCount = 0; | ||
matrixClient.on(ClientEvent.AccountData, (event: MatrixEvent) => { | ||
if (event.getType() === "org.matrix.msc3814") { | ||
setDehydrationCount++; | ||
} | ||
}); | ||
|
||
const crypto = matrixClient.getCrypto()!; | ||
fetchMock.config.overwriteRoutes = true; | ||
|
||
// start dehydration -- we start with no dehydrated device, and we | ||
// store the dehydrated device that we create | ||
fetchMock.get("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", { | ||
status: 404, | ||
body: { | ||
errcode: "M_NOT_FOUND", | ||
error: "Not found", | ||
}, | ||
}); | ||
let dehydratedDeviceBody: any; | ||
let dehydrationCount = 0; | ||
let resolveDehydrationPromise: () => void; | ||
fetchMock.put("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", (_, opts) => { | ||
dehydratedDeviceBody = JSON.parse(opts.body as string); | ||
dehydrationCount++; | ||
if (resolveDehydrationPromise) { | ||
resolveDehydrationPromise(); | ||
} | ||
return {}; | ||
}); | ||
await crypto.startDehydration(); | ||
|
||
expect(dehydrationCount).toEqual(1); | ||
|
||
// a week later, we should have created another dehydrated device | ||
const dehydrationPromise = new Promise<void>((resolve, reject) => { | ||
resolveDehydrationPromise = resolve; | ||
}); | ||
jest.advanceTimersByTime(7 * 24 * 60 * 60 * 1000); | ||
await dehydrationPromise; | ||
expect(dehydrationCount).toEqual(2); | ||
|
||
// restart dehydration -- rehydrate the device that we created above, | ||
// and create a new dehydrated device. We also set `createNewKey`, so | ||
// a new dehydration key will be set | ||
fetchMock.get("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", { | ||
device_id: dehydratedDeviceBody.device_id, | ||
device_data: dehydratedDeviceBody.device_data, | ||
}); | ||
const eventsResponse = jest.fn((url, opts) => { | ||
// rehydrating should make two calls to the /events endpoint. | ||
// The first time will return a single event, and the second | ||
// time will return no events (which will signal to the | ||
// rehydration function that it can stop) | ||
const body = JSON.parse(opts.body as string); | ||
const nextBatch = body.next_batch ?? "0"; | ||
const events = nextBatch === "0" ? [{ sender: "@alice:localhost", type: "m.dummy", content: {} }] : []; | ||
return { | ||
events, | ||
next_batch: nextBatch + "1", | ||
}; | ||
}); | ||
fetchMock.post( | ||
`path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device/${encodeURIComponent(dehydratedDeviceBody.device_id)}/events`, | ||
eventsResponse, | ||
); | ||
await crypto.startDehydration(true); | ||
expect(dehydrationCount).toEqual(3); | ||
|
||
expect(setDehydrationCount).toEqual(2); | ||
expect(eventsResponse.mock.calls).toHaveLength(2); | ||
|
||
matrixClient.stopClient(); | ||
}); | ||
}); | ||
|
||
/** create a new secret storage and cross-signing keys */ | ||
async function initializeSecretStorage( | ||
matrixClient: MatrixClient, | ||
userId: string, | ||
homeserverUrl: string, | ||
): Promise<void> { | ||
fetchMock.get("path:/_matrix/client/v3/room_keys/version", { | ||
status: 404, | ||
body: { | ||
errcode: "M_NOT_FOUND", | ||
error: "Not found", | ||
}, | ||
}); | ||
const e2eKeyReceiver = new E2EKeyReceiver(homeserverUrl); | ||
const e2eKeyResponder = new E2EKeyResponder(homeserverUrl); | ||
e2eKeyResponder.addKeyReceiver(userId, e2eKeyReceiver); | ||
fetchMock.post("path:/_matrix/client/v3/keys/device_signing/upload", {}); | ||
fetchMock.post("path:/_matrix/client/v3/keys/signatures/upload", {}); | ||
const accountData: Map<string, object> = new Map(); | ||
fetchMock.get("glob:http://*/_matrix/client/v3/user/*/account_data/*", (url, opts) => { | ||
const name = url.split("/").pop()!; | ||
const value = accountData.get(name); | ||
if (value) { | ||
return value; | ||
} else { | ||
return { | ||
status: 404, | ||
body: { | ||
errcode: "M_NOT_FOUND", | ||
error: "Not found", | ||
}, | ||
}; | ||
} | ||
}); | ||
fetchMock.put("glob:http://*/_matrix/client/v3/user/*/account_data/*", (url, opts) => { | ||
const name = url.split("/").pop()!; | ||
const value = JSON.parse(opts.body as string); | ||
accountData.set(name, value); | ||
matrixClient.emit(ClientEvent.AccountData, new MatrixEvent({ type: name, content: value })); | ||
return {}; | ||
}); | ||
|
||
await matrixClient.initRustCrypto(); | ||
const crypto = matrixClient.getCrypto()! as RustCrypto; | ||
// we need to process a sync so that the OlmMachine will upload keys | ||
await crypto.preprocessToDeviceMessages([]); | ||
await crypto.onSyncCompleted({}); | ||
|
||
// create initial secret storage | ||
async function createSecretStorageKey() { | ||
return { | ||
keyInfo: {} as AddSecretStorageKeyOpts, | ||
privateKey: new Uint8Array(32), | ||
}; | ||
} | ||
await matrixClient.bootstrapCrossSigning({ setupNewCrossSigning: true }); | ||
await matrixClient.bootstrapSecretStorage({ | ||
createSecretStorageKey, | ||
setupNewSecretStorage: true, | ||
setupNewKeyBackup: false, | ||
}); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.