@@ -40,6 +40,7 @@ import { logger } from "../../../src/logger";
40
40
import {
41
41
Category ,
42
42
createClient ,
43
+ CryptoEvent ,
43
44
IClaimOTKsResult ,
44
45
IContent ,
45
46
IDownloadKeyResult ,
@@ -61,9 +62,13 @@ import { ISyncResponder, SyncResponder } from "../../test-utils/SyncResponder";
61
62
import { escapeRegExp } from "../../../src/utils" ;
62
63
import { downloadDeviceToJsDevice } from "../../../src/rust-crypto/device-converter" ;
63
64
import { flushPromises } from "../../test-utils/flushPromises" ;
64
- import { mockInitialApiRequests , mockSetupCrossSigningRequests } from "../../test-utils/mockEndpoints" ;
65
+ import {
66
+ mockInitialApiRequests ,
67
+ mockSetupCrossSigningRequests ,
68
+ mockSetupMegolmBackupRequests ,
69
+ } from "../../test-utils/mockEndpoints" ;
65
70
import { AddSecretStorageKeyOpts , SECRET_STORAGE_ALGORITHM_V1_AES } from "../../../src/secret-storage" ;
66
- import { CryptoCallbacks } from "../../../src/crypto-api" ;
71
+ import { CryptoCallbacks , KeyBackupInfo } from "../../../src/crypto-api" ;
67
72
import { E2EKeyResponder } from "../../test-utils/E2EKeyResponder" ;
68
73
69
74
afterEach ( ( ) => {
@@ -2197,11 +2202,9 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
2197
2202
"express:/_matrix/client/v3/user/:userId/account_data/:type(m.secret_storage.*)" ,
2198
2203
( url : string , options : RequestInit ) => {
2199
2204
const content = JSON . parse ( options . body as string ) ;
2200
-
2201
2205
if ( content . key ) {
2202
2206
resolve ( content . key ) ;
2203
2207
}
2204
-
2205
2208
return { } ;
2206
2209
} ,
2207
2210
{ overwriteRoutes : true } ,
@@ -2228,6 +2231,68 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
2228
2231
} ) ;
2229
2232
}
2230
2233
2234
+ function awaitMegolmBackupKeyUpload ( ) : Promise < Record < string , { } > > {
2235
+ return new Promise ( ( resolve ) => {
2236
+ // Called when the megolm backup key is uploaded
2237
+ fetchMock . put (
2238
+ `express:/_matrix/client/v3/user/:userId/account_data/m.megolm_backup.v1` ,
2239
+ ( url : string , options : RequestInit ) => {
2240
+ const content = JSON . parse ( options . body as string ) ;
2241
+ resolve ( content . encrypted ) ;
2242
+ return { } ;
2243
+ } ,
2244
+ { overwriteRoutes : true } ,
2245
+ ) ;
2246
+ } ) ;
2247
+ }
2248
+
2249
+ async function bootstrapSecurity ( backupVersion : string ) : Promise < void > {
2250
+ mockSetupCrossSigningRequests ( ) ;
2251
+ mockSetupMegolmBackupRequests ( backupVersion ) ;
2252
+
2253
+ // promise which will resolve when a `KeyBackupStatus` event is emitted with `enabled: true`
2254
+ const backupStatusUpdate = new Promise < void > ( ( resolve ) => {
2255
+ aliceClient . on ( CryptoEvent . KeyBackupStatus , ( enabled ) => {
2256
+ if ( enabled ) {
2257
+ resolve ( ) ;
2258
+ }
2259
+ } ) ;
2260
+ } ) ;
2261
+
2262
+ const setupPromises = [
2263
+ awaitCrossSigningKeyUpload ( "master" ) ,
2264
+ awaitCrossSigningKeyUpload ( "user_signing" ) ,
2265
+ awaitCrossSigningKeyUpload ( "self_signing" ) ,
2266
+ awaitMegolmBackupKeyUpload ( ) ,
2267
+ ] ;
2268
+
2269
+ // Before setting up secret-storage, bootstrap cross-signing, so that the client has cross-signing keys.
2270
+ await aliceClient . getCrypto ( ) ! . bootstrapCrossSigning ( { } ) ;
2271
+
2272
+ // Now, when we bootstrap secret-storage, the cross-signing keys should be uploaded.
2273
+ const bootstrapPromise = aliceClient . getCrypto ( ) ! . bootstrapSecretStorage ( {
2274
+ setupNewSecretStorage : true ,
2275
+ createSecretStorageKey,
2276
+ setupNewKeyBackup : true ,
2277
+ } ) ;
2278
+
2279
+ // Wait for the key to be uploaded in the account data
2280
+ const secretStorageKey = await awaitSecretStorageKeyStoredInAccountData ( ) ;
2281
+
2282
+ // Return the newly created key in the sync response
2283
+ sendSyncResponse ( secretStorageKey ) ;
2284
+
2285
+ // Wait for the cross signing keys to be uploaded
2286
+ await Promise . all ( setupPromises ) ;
2287
+
2288
+ // wait for bootstrapSecretStorage to finished
2289
+ await bootstrapPromise ;
2290
+ // Finally ensure backup is working
2291
+ await aliceClient . getCrypto ( ) ! . checkKeyBackupAndEnable ( ) ;
2292
+
2293
+ await backupStatusUpdate ;
2294
+ }
2295
+
2231
2296
/**
2232
2297
* Send in the sync response the provided `secretStorageKey` into the account_data field
2233
2298
* The key is set for the `m.secret_storage.default_key` and `m.secret_storage.key.${secretStorageKey}` events
@@ -2385,6 +2450,89 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
2385
2450
expect ( userSigningKey [ secretStorageKey ] ) . toBeDefined ( ) ;
2386
2451
expect ( selfSigningKey [ secretStorageKey ] ) . toBeDefined ( ) ;
2387
2452
} ) ;
2453
+
2454
+ oldBackendOnly ( "should create a new megolm backup" , async ( ) => {
2455
+ const backupVersion = "abc" ;
2456
+ await bootstrapSecurity ( backupVersion ) ;
2457
+
2458
+ // Expect a backup to be available and used
2459
+ const activeBackup = await aliceClient . getCrypto ( ) ! . getActiveSessionBackupVersion ( ) ;
2460
+ expect ( activeBackup ) . toStrictEqual ( backupVersion ) ;
2461
+ } ) ;
2462
+
2463
+ oldBackendOnly ( "Reset key backup should create a new backup and update 4S" , async ( ) => {
2464
+ // First set up recovery
2465
+ const backupVersion = "1" ;
2466
+ await bootstrapSecurity ( backupVersion ) ;
2467
+
2468
+ const currentVersion = await aliceClient . getCrypto ( ) ! . getActiveSessionBackupVersion ( ) ;
2469
+ const currentBackupKey = await aliceClient . getCrypto ( ) ! . getSessionBackupPrivateKey ( ) ;
2470
+
2471
+ // we will call reset backup, it should delete the existing one, then setup a new one
2472
+ // Let's mock for that
2473
+
2474
+ // Mock delete and replace the GET to return 404 as soon as called
2475
+ const awaitDeleteCalled = new Promise < void > ( ( resolve ) => {
2476
+ fetchMock . delete (
2477
+ "express:/_matrix/client/v3/room_keys/version/:version" ,
2478
+ ( url : string , options : RequestInit ) => {
2479
+ fetchMock . get (
2480
+ "path:/_matrix/client/v3/room_keys/version" ,
2481
+ {
2482
+ status : 404 ,
2483
+ body : { errcode : "M_NOT_FOUND" , error : "Account data not found." } ,
2484
+ } ,
2485
+ { overwriteRoutes : true } ,
2486
+ ) ;
2487
+ resolve ( ) ;
2488
+ return { } ;
2489
+ } ,
2490
+ { overwriteRoutes : true } ,
2491
+ ) ;
2492
+ } ) ;
2493
+
2494
+ const newVersion = "2" ;
2495
+ fetchMock . post (
2496
+ "path:/_matrix/client/v3/room_keys/version" ,
2497
+ ( url , request ) => {
2498
+ const backupData : KeyBackupInfo = JSON . parse ( request . body ?. toString ( ) ?? "{}" ) ;
2499
+ backupData . version = newVersion ;
2500
+ backupData . count = 0 ;
2501
+ backupData . etag = "zer" ;
2502
+
2503
+ // update get call with new version
2504
+ fetchMock . get ( "path:/_matrix/client/v3/room_keys/version" , backupData , {
2505
+ overwriteRoutes : true ,
2506
+ } ) ;
2507
+ return {
2508
+ version : backupVersion ,
2509
+ } ;
2510
+ } ,
2511
+ { overwriteRoutes : true } ,
2512
+ ) ;
2513
+
2514
+ const newBackupStatusUpdate = new Promise < void > ( ( resolve ) => {
2515
+ aliceClient . on ( CryptoEvent . KeyBackupStatus , ( enabled ) => {
2516
+ if ( enabled ) {
2517
+ resolve ( ) ;
2518
+ }
2519
+ } ) ;
2520
+ } ) ;
2521
+
2522
+ const new4SUpload = awaitMegolmBackupKeyUpload ( ) ;
2523
+
2524
+ await aliceClient . getCrypto ( ) ! . resetKeyBackup ( ) ;
2525
+ await awaitDeleteCalled ;
2526
+ await newBackupStatusUpdate ;
2527
+ await new4SUpload ;
2528
+
2529
+ const nextVersion = await aliceClient . getCrypto ( ) ! . getActiveSessionBackupVersion ( ) ;
2530
+ const nextKey = await aliceClient . getCrypto ( ) ! . getSessionBackupPrivateKey ( ) ;
2531
+
2532
+ expect ( nextVersion ) . toBeDefined ( ) ;
2533
+ expect ( nextVersion ) . not . toEqual ( currentVersion ) ;
2534
+ expect ( nextKey ) . not . toEqual ( currentBackupKey ) ;
2535
+ } ) ;
2388
2536
} ) ;
2389
2537
2390
2538
describe ( "Incoming verification in a DM" , ( ) => {
0 commit comments