@@ -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,74 @@ 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
+ /**
2250
+ * Add all mocks needed to set up cross-signing, key backup, 4S and then
2251
+ * configure the account to have recovery.
2252
+ *
2253
+ * @param backupVersion - The version of the created backup
2254
+ */
2255
+ async function bootstrapSecurity ( backupVersion : string ) : Promise < void > {
2256
+ mockSetupCrossSigningRequests ( ) ;
2257
+ mockSetupMegolmBackupRequests ( backupVersion ) ;
2258
+
2259
+ // promise which will resolve when a `KeyBackupStatus` event is emitted with `enabled: true`
2260
+ const backupStatusUpdate = new Promise < void > ( ( resolve ) => {
2261
+ aliceClient . on ( CryptoEvent . KeyBackupStatus , ( enabled ) => {
2262
+ if ( enabled ) {
2263
+ resolve ( ) ;
2264
+ }
2265
+ } ) ;
2266
+ } ) ;
2267
+
2268
+ const setupPromises = [
2269
+ awaitCrossSigningKeyUpload ( "master" ) ,
2270
+ awaitCrossSigningKeyUpload ( "user_signing" ) ,
2271
+ awaitCrossSigningKeyUpload ( "self_signing" ) ,
2272
+ awaitMegolmBackupKeyUpload ( ) ,
2273
+ ] ;
2274
+
2275
+ // Before setting up secret-storage, bootstrap cross-signing, so that the client has cross-signing keys.
2276
+ await aliceClient . getCrypto ( ) ! . bootstrapCrossSigning ( { } ) ;
2277
+
2278
+ // Now, when we bootstrap secret-storage, the cross-signing keys should be uploaded.
2279
+ const bootstrapPromise = aliceClient . getCrypto ( ) ! . bootstrapSecretStorage ( {
2280
+ setupNewSecretStorage : true ,
2281
+ createSecretStorageKey,
2282
+ setupNewKeyBackup : true ,
2283
+ } ) ;
2284
+
2285
+ // Wait for the key to be uploaded in the account data
2286
+ const secretStorageKey = await awaitSecretStorageKeyStoredInAccountData ( ) ;
2287
+
2288
+ // Return the newly created key in the sync response
2289
+ sendSyncResponse ( secretStorageKey ) ;
2290
+
2291
+ // Wait for the cross signing keys to be uploaded
2292
+ await Promise . all ( setupPromises ) ;
2293
+
2294
+ // wait for bootstrapSecretStorage to finished
2295
+ await bootstrapPromise ;
2296
+ // Finally ensure backup is working
2297
+ await aliceClient . getCrypto ( ) ! . checkKeyBackupAndEnable ( ) ;
2298
+
2299
+ await backupStatusUpdate ;
2300
+ }
2301
+
2231
2302
/**
2232
2303
* Send in the sync response the provided `secretStorageKey` into the account_data field
2233
2304
* The key is set for the `m.secret_storage.default_key` and `m.secret_storage.key.${secretStorageKey}` events
@@ -2385,6 +2456,95 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
2385
2456
expect ( userSigningKey [ secretStorageKey ] ) . toBeDefined ( ) ;
2386
2457
expect ( selfSigningKey [ secretStorageKey ] ) . toBeDefined ( ) ;
2387
2458
} ) ;
2459
+
2460
+ oldBackendOnly ( "should create a new megolm backup" , async ( ) => {
2461
+ const backupVersion = "abc" ;
2462
+ await bootstrapSecurity ( backupVersion ) ;
2463
+
2464
+ // Expect a backup to be available and used
2465
+ const activeBackup = await aliceClient . getCrypto ( ) ! . getActiveSessionBackupVersion ( ) ;
2466
+ expect ( activeBackup ) . toStrictEqual ( backupVersion ) ;
2467
+ } ) ;
2468
+
2469
+ oldBackendOnly ( "Reset key backup should create a new backup and update 4S" , async ( ) => {
2470
+ // First set up 4S and key backup
2471
+ const backupVersion = "1" ;
2472
+ await bootstrapSecurity ( backupVersion ) ;
2473
+
2474
+ const currentVersion = await aliceClient . getCrypto ( ) ! . getActiveSessionBackupVersion ( ) ;
2475
+ const currentBackupKey = await aliceClient . getCrypto ( ) ! . getSessionBackupPrivateKey ( ) ;
2476
+
2477
+ // we will call reset backup, it should delete the existing one, then setup a new one
2478
+ // Let's mock for that
2479
+
2480
+ // Mock delete and replace the GET to return 404 as soon as called
2481
+ const awaitDeleteCalled = new Promise < void > ( ( resolve ) => {
2482
+ fetchMock . delete (
2483
+ "express:/_matrix/client/v3/room_keys/version/:version" ,
2484
+ ( url : string , options : RequestInit ) => {
2485
+ fetchMock . get (
2486
+ "path:/_matrix/client/v3/room_keys/version" ,
2487
+ {
2488
+ status : 404 ,
2489
+ body : { errcode : "M_NOT_FOUND" , error : "Account data not found." } ,
2490
+ } ,
2491
+ { overwriteRoutes : true } ,
2492
+ ) ;
2493
+ resolve ( ) ;
2494
+ return { } ;
2495
+ } ,
2496
+ { overwriteRoutes : true } ,
2497
+ ) ;
2498
+ } ) ;
2499
+
2500
+ const newVersion = "2" ;
2501
+ fetchMock . post (
2502
+ "path:/_matrix/client/v3/room_keys/version" ,
2503
+ ( url , request ) => {
2504
+ const backupData : KeyBackupInfo = JSON . parse ( request . body ?. toString ( ) ?? "{}" ) ;
2505
+ backupData . version = newVersion ;
2506
+ backupData . count = 0 ;
2507
+ backupData . etag = "zer" ;
2508
+
2509
+ // update get call with new version
2510
+ fetchMock . get ( "path:/_matrix/client/v3/room_keys/version" , backupData , {
2511
+ overwriteRoutes : true ,
2512
+ } ) ;
2513
+ return {
2514
+ version : backupVersion ,
2515
+ } ;
2516
+ } ,
2517
+ { overwriteRoutes : true } ,
2518
+ ) ;
2519
+
2520
+ const newBackupStatusUpdate = new Promise < void > ( ( resolve ) => {
2521
+ aliceClient . on ( CryptoEvent . KeyBackupStatus , ( enabled ) => {
2522
+ if ( enabled ) {
2523
+ resolve ( ) ;
2524
+ }
2525
+ } ) ;
2526
+ } ) ;
2527
+
2528
+ const newBackupUploadPromise = awaitMegolmBackupKeyUpload ( ) ;
2529
+
2530
+ await aliceClient . getCrypto ( ) ! . resetKeyBackup ( ) ;
2531
+ await awaitDeleteCalled ;
2532
+ await newBackupStatusUpdate ;
2533
+ await newBackupUploadPromise ;
2534
+
2535
+ const nextVersion = await aliceClient . getCrypto ( ) ! . getActiveSessionBackupVersion ( ) ;
2536
+ const nextKey = await aliceClient . getCrypto ( ) ! . getSessionBackupPrivateKey ( ) ;
2537
+
2538
+ expect ( nextVersion ) . toBeDefined ( ) ;
2539
+ expect ( nextVersion ) . not . toEqual ( currentVersion ) ;
2540
+ expect ( nextKey ) . not . toEqual ( currentBackupKey ) ;
2541
+
2542
+ // Test deletion of the backup
2543
+ await aliceClient . getCrypto ( ) ! . deleteKeyBackupVersion ( nextVersion ! ) ;
2544
+ await aliceClient . getCrypto ( ) ! . checkKeyBackupAndEnable ( ) ;
2545
+ // XXX Legacy crypto does not update 4S when deleting backup; should ensure that rust implem does it.
2546
+ expect ( await aliceClient . getCrypto ( ) ! . getActiveSessionBackupVersion ( ) ) . toBeNull ( ) ;
2547
+ } ) ;
2388
2548
} ) ;
2389
2549
2390
2550
describe ( "Incoming verification in a DM" , ( ) => {
0 commit comments