Skip to content

Commit a3cea8c

Browse files
authored
Add crypto methods for export and import of secrets bundle (#4227)
* Add crypto methods for OIDC QR code login Signed-off-by: Michael Telatynski <[email protected]> * Improve test Signed-off-by: Michael Telatynski <[email protected]> * Revert test due to hang inside Rust. Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Update test name Signed-off-by: Michael Telatynski <[email protected]> * Update test name Signed-off-by: Michael Telatynski <[email protected]> --------- Signed-off-by: Michael Telatynski <[email protected]>
1 parent c88487d commit a3cea8c

File tree

4 files changed

+117
-0
lines changed

4 files changed

+117
-0
lines changed

Diff for: spec/unit/rust-crypto/rust-crypto.spec.ts

+38
Original file line numberDiff line numberDiff line change
@@ -1515,6 +1515,44 @@ describe("RustCrypto", () => {
15151515
expect(await rustCrypto.isDehydrationSupported()).toBe(true);
15161516
});
15171517
});
1518+
1519+
describe("import & export secrets bundle", () => {
1520+
let rustCrypto: RustCrypto;
1521+
1522+
beforeEach(async () => {
1523+
rustCrypto = await makeTestRustCrypto(
1524+
new MatrixHttpApi(new TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>(), {
1525+
baseUrl: "http://server/",
1526+
prefix: "",
1527+
onlyData: true,
1528+
}),
1529+
testData.TEST_USER_ID,
1530+
);
1531+
});
1532+
1533+
it("should throw an error if there is nothing to export", async () => {
1534+
await expect(rustCrypto.exportsSecretsBundle()).rejects.toThrow(
1535+
"The store doesn't contain any cross-signing keys",
1536+
);
1537+
});
1538+
1539+
it("should correctly import & export a secrets bundle", async () => {
1540+
const bundle = {
1541+
cross_signing: {
1542+
master_key: "bMnVpkHI4S2wXRxy+IpaKM5PIAUUkl6DE+n0YLIW/qs",
1543+
user_signing_key: "8tlgLjUrrb/zGJo4YKGhDTIDCEjtJTAS/Sh2AGNLuIo",
1544+
self_signing_key: "pfDknmP5a0fVVRE54zhkUgJfzbNmvKcNfIWEW796bQs",
1545+
},
1546+
backup: {
1547+
algorithm: "m.megolm_backup.v1.curve25519-aes-sha2",
1548+
key: "bYYv3aFLQ49jMNcOjuTtBY9EKDby2x1m3gfX81nIKRQ",
1549+
backup_version: "9",
1550+
},
1551+
};
1552+
await rustCrypto.importSecretsBundle(bundle);
1553+
await expect(rustCrypto.exportsSecretsBundle()).resolves.toEqual(expect.objectContaining(bundle));
1554+
});
1555+
});
15181556
});
15191557

15201558
/** Build a MatrixHttpApi instance */

Diff for: src/@types/matrix-sdk-crypto-wasm.d.ts

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
Copyright 2024 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import type * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
18+
19+
declare module "@matrix-org/matrix-sdk-crypto-wasm" {
20+
interface OlmMachine {
21+
importSecretsBundle(bundle: RustSdkCryptoJs.SecretsBundle): Promise<void>;
22+
exportSecretsBundle(): Promise<RustSdkCryptoJs.SecretsBundle>;
23+
}
24+
25+
interface SecretsBundle {
26+
// eslint-disable-next-line @typescript-eslint/naming-convention
27+
to_json(): Promise<{
28+
cross_signing: {
29+
master_key: string;
30+
self_signing_key: string;
31+
user_signing_key: string;
32+
};
33+
backup?: {
34+
algorithm: string;
35+
key: string;
36+
backup_version: string;
37+
};
38+
}>;
39+
}
40+
}

Diff for: src/crypto-api.ts

+18
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
import type { SecretsBundle } from "@matrix-org/matrix-sdk-crypto-wasm";
1718
import type { IMegolmSessionData } from "./@types/crypto";
1819
import { Room } from "./models/room";
1920
import { DeviceMap } from "./models/device";
@@ -532,6 +533,23 @@ export interface CryptoApi {
532533
* to false.
533534
*/
534535
startDehydration(createNewKey?: boolean): Promise<void>;
536+
537+
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
538+
//
539+
// Import/export of secret keys
540+
//
541+
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
542+
543+
/**
544+
* Export secrets bundle for transmitting to another device as part of OIDC QR login
545+
*/
546+
exportSecretsBundle?(): Promise<Awaited<ReturnType<SecretsBundle["to_json"]>>>;
547+
548+
/**
549+
* Import secrets bundle transmitted from another device.
550+
* @param secrets - The secrets bundle received from the other device
551+
*/
552+
importSecretsBundle?(secrets: Awaited<ReturnType<SecretsBundle["to_json"]>>): Promise<void>;
535553
}
536554

537555
/** A reason code for a failure to decrypt an event. */

Diff for: src/rust-crypto/rust-crypto.ts

+21
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
CrossSigningKey,
3939
CrossSigningKeyInfo,
4040
CrossSigningStatus,
41+
CryptoApi,
4142
CryptoCallbacks,
4243
Curve25519AuthData,
4344
DecryptionFailureCode,
@@ -1165,6 +1166,26 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
11651166
this.checkKeyBackupAndEnable();
11661167
}
11671168

1169+
/**
1170+
* Implementation of {@link CryptoApi#importSecretsBundle}.
1171+
*/
1172+
public async importSecretsBundle(
1173+
secrets: Parameters<NonNullable<CryptoApi["importSecretsBundle"]>>[0],
1174+
): Promise<void> {
1175+
const secretsBundle = RustSdkCryptoJs.SecretsBundle.from_json(secrets);
1176+
await this.getOlmMachineOrThrow().importSecretsBundle(secretsBundle); // this method frees the SecretsBundle
1177+
}
1178+
1179+
/**
1180+
* Implementation of {@link CryptoApi#exportSecretsBundle}.
1181+
*/
1182+
public async exportsSecretsBundle(): ReturnType<NonNullable<CryptoApi["exportSecretsBundle"]>> {
1183+
const secretsBundle = await this.getOlmMachineOrThrow().exportSecretsBundle();
1184+
const secrets = secretsBundle.to_json();
1185+
secretsBundle.free();
1186+
return secrets;
1187+
}
1188+
11681189
/**
11691190
* Signs the given object with the current device and current identity (if available).
11701191
* As defined in {@link https://spec.matrix.org/v1.8/appendices/#signing-json | Signing JSON}.

0 commit comments

Comments
 (0)