From a3cea8ce7d93199b33d48160b4a4d2bd125437c1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 5 Jun 2024 10:27:20 +0100 Subject: [PATCH] Add crypto methods for export and import of secrets bundle (#4227) * Add crypto methods for OIDC QR code login Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Improve test Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Revert test due to hang inside Rust. Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update test name Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update test name Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- spec/unit/rust-crypto/rust-crypto.spec.ts | 38 +++++++++++++++++++++ src/@types/matrix-sdk-crypto-wasm.d.ts | 40 +++++++++++++++++++++++ src/crypto-api.ts | 18 ++++++++++ src/rust-crypto/rust-crypto.ts | 21 ++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 src/@types/matrix-sdk-crypto-wasm.d.ts diff --git a/spec/unit/rust-crypto/rust-crypto.spec.ts b/spec/unit/rust-crypto/rust-crypto.spec.ts index 57c1066d59..a5a6c16543 100644 --- a/spec/unit/rust-crypto/rust-crypto.spec.ts +++ b/spec/unit/rust-crypto/rust-crypto.spec.ts @@ -1515,6 +1515,44 @@ describe("RustCrypto", () => { expect(await rustCrypto.isDehydrationSupported()).toBe(true); }); }); + + describe("import & export secrets bundle", () => { + let rustCrypto: RustCrypto; + + beforeEach(async () => { + rustCrypto = await makeTestRustCrypto( + new MatrixHttpApi(new TypedEventEmitter(), { + baseUrl: "http://server/", + prefix: "", + onlyData: true, + }), + testData.TEST_USER_ID, + ); + }); + + it("should throw an error if there is nothing to export", async () => { + await expect(rustCrypto.exportsSecretsBundle()).rejects.toThrow( + "The store doesn't contain any cross-signing keys", + ); + }); + + it("should correctly import & export a secrets bundle", async () => { + const bundle = { + cross_signing: { + master_key: "bMnVpkHI4S2wXRxy+IpaKM5PIAUUkl6DE+n0YLIW/qs", + user_signing_key: "8tlgLjUrrb/zGJo4YKGhDTIDCEjtJTAS/Sh2AGNLuIo", + self_signing_key: "pfDknmP5a0fVVRE54zhkUgJfzbNmvKcNfIWEW796bQs", + }, + backup: { + algorithm: "m.megolm_backup.v1.curve25519-aes-sha2", + key: "bYYv3aFLQ49jMNcOjuTtBY9EKDby2x1m3gfX81nIKRQ", + backup_version: "9", + }, + }; + await rustCrypto.importSecretsBundle(bundle); + await expect(rustCrypto.exportsSecretsBundle()).resolves.toEqual(expect.objectContaining(bundle)); + }); + }); }); /** Build a MatrixHttpApi instance */ diff --git a/src/@types/matrix-sdk-crypto-wasm.d.ts b/src/@types/matrix-sdk-crypto-wasm.d.ts new file mode 100644 index 0000000000..591ecd6406 --- /dev/null +++ b/src/@types/matrix-sdk-crypto-wasm.d.ts @@ -0,0 +1,40 @@ +/* +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 type * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm"; + +declare module "@matrix-org/matrix-sdk-crypto-wasm" { + interface OlmMachine { + importSecretsBundle(bundle: RustSdkCryptoJs.SecretsBundle): Promise; + exportSecretsBundle(): Promise; + } + + interface SecretsBundle { + // eslint-disable-next-line @typescript-eslint/naming-convention + to_json(): Promise<{ + cross_signing: { + master_key: string; + self_signing_key: string; + user_signing_key: string; + }; + backup?: { + algorithm: string; + key: string; + backup_version: string; + }; + }>; + } +} diff --git a/src/crypto-api.ts b/src/crypto-api.ts index 7dfdfbfcf0..740d60dc8b 100644 --- a/src/crypto-api.ts +++ b/src/crypto-api.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import type { SecretsBundle } from "@matrix-org/matrix-sdk-crypto-wasm"; import type { IMegolmSessionData } from "./@types/crypto"; import { Room } from "./models/room"; import { DeviceMap } from "./models/device"; @@ -532,6 +533,23 @@ export interface CryptoApi { * to false. */ startDehydration(createNewKey?: boolean): Promise; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + // Import/export of secret keys + // + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Export secrets bundle for transmitting to another device as part of OIDC QR login + */ + exportSecretsBundle?(): Promise>>; + + /** + * Import secrets bundle transmitted from another device. + * @param secrets - The secrets bundle received from the other device + */ + importSecretsBundle?(secrets: Awaited>): Promise; } /** A reason code for a failure to decrypt an event. */ diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index 8cbfba4b0c..c1b8f05195 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -38,6 +38,7 @@ import { CrossSigningKey, CrossSigningKeyInfo, CrossSigningStatus, + CryptoApi, CryptoCallbacks, Curve25519AuthData, DecryptionFailureCode, @@ -1165,6 +1166,26 @@ export class RustCrypto extends TypedEventEmitter>[0], + ): Promise { + const secretsBundle = RustSdkCryptoJs.SecretsBundle.from_json(secrets); + await this.getOlmMachineOrThrow().importSecretsBundle(secretsBundle); // this method frees the SecretsBundle + } + + /** + * Implementation of {@link CryptoApi#exportSecretsBundle}. + */ + public async exportsSecretsBundle(): ReturnType> { + const secretsBundle = await this.getOlmMachineOrThrow().exportSecretsBundle(); + const secrets = secretsBundle.to_json(); + secretsBundle.free(); + return secrets; + } + /** * Signs the given object with the current device and current identity (if available). * As defined in {@link https://spec.matrix.org/v1.8/appendices/#signing-json | Signing JSON}.