Skip to content
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

RustCrypto | Implement keybackup loop #3652

Merged
merged 21 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
],
"dependencies": {
"@babel/runtime": "^7.12.5",
"@matrix-org/matrix-sdk-crypto-wasm": "^1.2.0",
"@matrix-org/matrix-sdk-crypto-wasm": "^1.2.1",
"another-json": "^0.2.0",
"bs58": "^5.0.0",
"content-type": "^1.0.4",
Expand Down
391 changes: 390 additions & 1 deletion spec/integ/crypto/megolm-backup.spec.ts

Large diffs are not rendered by default.

46 changes: 45 additions & 1 deletion spec/test-utils/test-data/generate-test-data.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from canonicaljson import encode_canonical_json
from cryptography.hazmat.primitives.asymmetric import ed25519, x25519
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
from random import randbytes

# input data
TEST_USER_ID = "@alice:localhost"
Expand Down Expand Up @@ -123,7 +124,7 @@ def main() -> None:
* Do not edit by hand! This file is generated by `./generate-test-data.py`
*/

import {{ IDeviceKeys }} from "../../../src/@types/crypto";
import {{ IDeviceKeys, IMegolmSessionData }} from "../../../src/@types/crypto";
import {{ IDownloadKeyResult }} from "../../../src";
import {{ KeyBackupInfo }} from "../../../src/crypto-api";

Expand Down Expand Up @@ -167,6 +168,20 @@ def main() -> None:

/** Signed backup data, suitable for return from `GET /_matrix/client/v3/room_keys/keys/{{roomId}}/{{sessionId}}` */
export const SIGNED_BACKUP_DATA: KeyBackupInfo = { json.dumps(backup_data, indent=4) };

export const MEGOLM_SESSION_DATA_ARRAY: IMegolmSessionData[] = [
BillCarsonFr marked this conversation as resolved.
Show resolved Hide resolved
{
json.dumps(build_exported_megolm_key(), indent=8)
},
{
json.dumps(build_exported_megolm_key(), indent=8)
},
];
BillCarsonFr marked this conversation as resolved.
Show resolved Hide resolved

/** An exported megolm session */
export const MEGOLM_SESSION_DATA: IMegolmSessionData = {
json.dumps(build_exported_megolm_key(), indent=4)
};
BillCarsonFr marked this conversation as resolved.
Show resolved Hide resolved
""",
end="",
)
Expand Down Expand Up @@ -265,6 +280,35 @@ def sign_json(json_object: dict, private_key: ed25519.Ed25519PrivateKey) -> str:

return signature_base64

def build_exported_megolm_key() -> dict:
richvdh marked this conversation as resolved.
Show resolved Hide resolved
index = int(0)
BillCarsonFr marked this conversation as resolved.
Show resolved Hide resolved
private_key = ed25519.Ed25519PrivateKey.generate()
# Just use radom bytes for the ratchet parts
ratchet = randbytes(32 * 4)
Comment on lines +293 to +294
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to avoid randomness in this script, so that when we re-run it, only important things change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a static seed to have stable random at each generation

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we just hardcode the strings, rather than having to seed the random generator and call randbytes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks to me that it has best of the two words, it's stable and later on I can add more keys easilly test key upgrade and more

# exported key, start with version byte
exported_key = bytearray(b'\x01')
exported_key += index.to_bytes(4, 'big')
exported_key += ratchet
# KPub
exported_key += private_key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)


megolm_export = {
"algorithm": "m.megolm.v1.aes-sha2",
"room_id": "!roomA:example.org",
"sender_key": "/Bu9e34hUClhddpf4E5gu5qEAdMY31+1A9HbiAeeQgo",
"session_id": encode_base64(
private_key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
),
"session_key": encode_base64(exported_key),
"sender_claimed_keys": {
"ed25519": encode_base64(ed25519.Ed25519PrivateKey.generate().public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)),
},
"forwarding_curve25519_key_chain": [],
}

return megolm_export


if __name__ == "__main__":
main()
42 changes: 40 additions & 2 deletions spec/test-utils/test-data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Do not edit by hand! This file is generated by `./generate-test-data.py`
*/

import { IDeviceKeys } from "../../../src/@types/crypto";
import { IDeviceKeys, IMegolmSessionData } from "../../../src/@types/crypto";
import { IDownloadKeyResult } from "../../../src";
import { KeyBackupInfo } from "../../../src/crypto-api";

Expand Down Expand Up @@ -115,4 +115,42 @@ export const SIGNED_BACKUP_DATA: KeyBackupInfo = {
}
}
}
};
};

export const MEGOLM_SESSION_DATA_ARRAY: IMegolmSessionData[] = [
{
"algorithm": "m.megolm.v1.aes-sha2",
"room_id": "!roomA:example.org",
"sender_key": "/Bu9e34hUClhddpf4E5gu5qEAdMY31+1A9HbiAeeQgo",
"session_id": "B/1owFh7K0r8cAVzQJnYxVpSmR+Cutiy7DTLRPsIsOI",
"session_key": "AQAAAADdFxBB2aZSfIHX9uOy1k9RHAtOYavhB2xBG1BiN5fXLTnFj9WMNGw1puBHbc9Xz1OiO79AUHOj/Yz1aiqn/PQBoBY7n7onN3vRrEUOk8PXpUfKGMwDN6IX1eyXD9jg36CQCTp2rtGZIitT4uO1rEC1vyI1F/9FQ+zFS1FKBTnAoQf9aMBYeytK/HAFc0CZ2MVaUpkfgrrYsuw0y0T7CLDi",
"sender_claimed_keys": {
"ed25519": "0Z+1+PWGErCmWx9fszojigz8Axh51Hgu9l8apPKU80k"
},
"forwarding_curve25519_key_chain": []
},
{
"algorithm": "m.megolm.v1.aes-sha2",
"room_id": "!roomA:example.org",
"sender_key": "/Bu9e34hUClhddpf4E5gu5qEAdMY31+1A9HbiAeeQgo",
"session_id": "mG/goAa0BeALT2sjRL8bVTvYow1RfTFmcVRU22S1W4k",
"session_key": "AQAAAADlTISccgGBYg/MSQnMlBve5GWzxckGQ0tpiDSKARDF8JhOC7GJxhanJtBT/qbiiEdo/b+cfNjLM2p5fld1R+6v1vzPCrOD2wDH99n46FJAncHXZWjiaNKy35EdZK+v75BkTqfoNluawv4n3qgHa8Z8DK0jLFOfwXG3okTZX28fDJhv4KAGtAXgC09rI0S/G1U72KMNUX0xZnFUVNtktVuJ",
"sender_claimed_keys": {
"ed25519": "XXGabP9k45NJ1cWRcZ3NyLAkQ7BKNmSoW5MjhfsnqYY"
},
"forwarding_curve25519_key_chain": []
},
];

/** An exported megolm session */
export const MEGOLM_SESSION_DATA: IMegolmSessionData = {
"algorithm": "m.megolm.v1.aes-sha2",
"room_id": "!roomA:example.org",
"sender_key": "/Bu9e34hUClhddpf4E5gu5qEAdMY31+1A9HbiAeeQgo",
"session_id": "0GDRENodidyou+W+od+Y8AyHLJe+7Jo+rzDwjSPDFH8",
"session_key": "AQAAAAB+5PGbWI5AOqKXrwGYd8pDOFVbiL4vzLSzVqMm2IrlR6SUxC6zYS2FUxKTaJLnnZKblsSOwMj5Uz7L7ld9FhZ4JTKpb+KtekVmohGjmzEvWKNBnPoqZRRg6gD25FnnxYImXwAhuBl04TG76P4FRzMCb4AlHIOYiQf5b8EIGU6h3tBg0RDaHYncqLvlvqHfmPAMhyyXvuyaPq8w8I0jwxR/",
"sender_claimed_keys": {
"ed25519": "zFsHIa5597r9zods42B3KD1laMBdlaTBgwp+rgkh14o"
},
"forwarding_curve25519_key_chain": []
};
31 changes: 3 additions & 28 deletions spec/unit/rust-crypto/rust-crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,32 +61,7 @@ describe("RustCrypto", () => {
);

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: [],
},
];
const someRoomKeys = testData.MEGOLM_SESSION_DATA_ARRAY;
let importTotal = 0;
const opt: ImportRoomKeysOpts = {
progressCallback: (stage) => {
Expand All @@ -95,11 +70,11 @@ describe("RustCrypto", () => {
};
await rustCrypto.importRoomKeys(someRoomKeys, opt);

expect(importTotal).toBe(2);
expect(importTotal).toBe(someRoomKeys.length);

const keys = await rustCrypto.exportRoomKeys();
expect(Array.isArray(keys)).toBeTruthy();
expect(keys.length).toBe(2);
expect(keys.length).toBe(someRoomKeys.length);

const aSession = someRoomKeys[0];

Expand Down
2 changes: 2 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2253,6 +2253,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
CryptoEvent.VerificationRequestReceived,
CryptoEvent.UserTrustStatusChanged,
CryptoEvent.KeyBackupStatus,
CryptoEvent.KeyBackupSessionsRemaining,
CryptoEvent.KeyBackupFailed,
]);
}

Expand Down
8 changes: 8 additions & 0 deletions src/crypto/backup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ export class BackupManager {
* @param maxDelay - Maximum delay to wait in ms. 0 means no delay.
*/
public async scheduleKeyBackupSend(maxDelay = 10000): Promise<void> {
logger.debug(`Key backup: scheduleKeyBackupSend currentSending:${this.sendingBackups} delay:${maxDelay}`);
if (this.sendingBackups) return;

this.sendingBackups = true;
Expand Down Expand Up @@ -474,6 +475,9 @@ export class BackupManager {
(<MatrixError>err).data.errcode == "M_NOT_FOUND" ||
(<MatrixError>err).data.errcode == "M_WRONG_ROOM_KEYS_VERSION"
) {
// fix do it now as the await check might trigger a new call to the loop before
BillCarsonFr marked this conversation as resolved.
Show resolved Hide resolved
// the finally is called
this.sendingBackups = false;
// Re-check key backup status on error, so we can be
// sure to present the current situation when asked.
await this.checkKeyBackup();
Expand All @@ -494,6 +498,10 @@ export class BackupManager {
return;
}
}
} catch (err) {
// No one actually check errors on this promise, it's spawned internally.
BillCarsonFr marked this conversation as resolved.
Show resolved Hide resolved
// Just log, apps/client should use events to check status
logger.log(`Backup loop failed ${err}`);
} finally {
this.sendingBackups = false;
}
Expand Down
7 changes: 6 additions & 1 deletion src/rust-crypto/OutgoingRequestProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ export class OutgoingRequestProcessor {
} else if (msg instanceof SignatureUploadRequest) {
resp = await this.rawJsonRequest(Method.Post, "/_matrix/client/v3/keys/signatures/upload", {}, msg.body);
} else if (msg instanceof KeysBackupRequest) {
resp = await this.rawJsonRequest(Method.Put, "/_matrix/client/v3/room_keys/keys", {}, msg.body);
resp = await this.rawJsonRequest(
Method.Put,
"/_matrix/client/v3/room_keys/keys",
{ version: msg.version },
msg.body,
);
} else if (msg instanceof ToDeviceRequest) {
const path =
`/_matrix/client/v3/sendToDevice/${encodeURIComponent(msg.event_type)}/` +
Expand Down
Loading
Loading