@@ -84,6 +84,7 @@ import {
8484 createOlmAccount ,
8585 createOlmSession ,
8686 encryptGroupSessionKey ,
87+ encryptOlmEvent ,
8788 encryptMegolmEvent ,
8889 encryptMegolmEventRawPlainText ,
8990 establishOlmSession ,
@@ -2311,4 +2312,107 @@ describe("crypto", () => {
23112312 ) ;
23122313 }
23132314 } ) ;
2315+
2316+ describe ( "secret pushing" , ( ) => {
2317+ it ( "should push a new backup key when a new backup key is set" , async ( ) => {
2318+ // setup: alice has another device, DEVICE_ID, which is verified
2319+ const crypto = aliceClient . getCrypto ( ) ! ;
2320+ expectAliceKeyQuery ( getTestKeysQueryResponse ( "@alice:localhost" ) ) ;
2321+ await startClientAndAwaitFirstSync ( ) ;
2322+ const devices = await aliceClient . getCrypto ( ) ! . getUserDeviceInfo ( [ "@alice:localhost" ] ) ;
2323+ expect ( devices . get ( "@alice:localhost" ) ! . keys ( ) ) . toContain ( "DEVICE_ID" ) ;
2324+ await crypto . setDeviceVerified ( "@alice:localhost" , "DEVICE_ID" ) ;
2325+
2326+ expectAliceKeyClaim ( getTestKeysClaimResponse ( "@alice:localhost" ) ) ;
2327+
2328+ // when we set a new backup key
2329+ fetchMock . get ( "path:/_matrix/client/v3/room_keys/version" , {
2330+ status : 404 ,
2331+ body : { errcode : "M_NOT_FOUND" , error : "No current backup version." } ,
2332+ } ) ;
2333+ fetchMock . post ( "path:/_matrix/client/v3/room_keys/version" , {
2334+ status : 200 ,
2335+ body : { version : "1" } ,
2336+ } ) ;
2337+ const secretPushPromise = new Promise < any > ( ( resolve ) => {
2338+ fetchMock . putOnce ( new RegExp ( "/sendToDevice/m.room.encrypted/" ) , ( callLog ) : RouteResponse => {
2339+ const content = JSON . parse ( callLog . options . body as string ) ;
2340+ resolve ( content ) ;
2341+ return { } ;
2342+ } ) ;
2343+ } ) ;
2344+
2345+ await crypto . resetKeyBackup ( ) ;
2346+
2347+ // we expect the other device to get a secret push
2348+ const content = await secretPushPromise ;
2349+ const curve25519key = JSON . parse ( testOlmAccount . identity_keys ( ) ) . curve25519 ;
2350+ const ciphertext = content . messages [ "@alice:localhost" ] . DEVICE_ID . ciphertext [ curve25519key ] ;
2351+ const olmSession = new Olm . Session ( ) ;
2352+ olmSession . create_inbound ( testOlmAccount , ciphertext . body ) ;
2353+ const decrypted = JSON . parse ( olmSession . decrypt ( 0 , ciphertext . body ) ) ;
2354+ expect ( decrypted . type ) . toBe ( "io.element.msc4385.secret.push" ) ;
2355+ expect ( decrypted . content . name ) . toBe ( "m.megolm_backup.v1" ) ;
2356+ } ) ;
2357+
2358+ it ( "should receive pushed backup key" , async ( ) => {
2359+ // setup: alice has another device, DEVICE_ID, which is verified,
2360+ // and has a key backup set up and signed by DEVICE_ID
2361+ const crypto = aliceClient . getCrypto ( ) ! ;
2362+ expectAliceKeyQuery ( getTestKeysQueryResponse ( "@alice:localhost" ) ) ;
2363+ fetchMock . get ( "path:/_matrix/client/v3/room_keys/version" , testData . SIGNED_BACKUP_DATA ) ;
2364+ await startClientAndAwaitFirstSync ( ) ;
2365+ const devices = await aliceClient . getCrypto ( ) ! . getUserDeviceInfo ( [ "@alice:localhost" ] ) ;
2366+ expect ( devices . get ( "@alice:localhost" ) ! . keys ( ) ) . toContain ( "DEVICE_ID" ) ;
2367+ await crypto . setDeviceVerified ( "@alice:localhost" , "DEVICE_ID" ) ;
2368+
2369+ expectAliceKeyClaim ( getTestKeysClaimResponse ( "@alice:localhost" ) ) ;
2370+
2371+ // after we push the backup key to alice...
2372+
2373+ const senderIdentityKeys = JSON . parse ( testOlmAccount . identity_keys ( ) ) ;
2374+ const aliceDeviceKeys = await crypto . getOwnDeviceKeys ( ) ;
2375+ const p2pSession = await createOlmSession ( testOlmAccount , keyReceiver ) ;
2376+ const secretPush = encryptOlmEvent ( {
2377+ sender : "@alice:localhost" ,
2378+ senderKey : senderIdentityKeys . curve25519 ,
2379+ senderSigningKey : senderIdentityKeys . ed25519 ,
2380+ p2pSession,
2381+ recipient : "@alice:localhost" ,
2382+ recipientCurve25519Key : aliceDeviceKeys . curve25519 ,
2383+ recipientEd25519Key : aliceDeviceKeys . ed25519 ,
2384+ plaincontent : {
2385+ secret : testData . BACKUP_DECRYPTION_KEY_BASE64 ,
2386+ name : "m.megolm_backup.v1" ,
2387+ } ,
2388+ plaintype : "io.element.msc4385.secret.push" ,
2389+ } ) ;
2390+
2391+ const syncResponse = {
2392+ next_batch : 1 ,
2393+ to_device : {
2394+ events : [ secretPush ] ,
2395+ } ,
2396+ } ;
2397+
2398+ const backupKeyReceivedPromise = new Promise < string > ( ( resolve ) => {
2399+ aliceClient . on ( CryptoEvent . KeyBackupDecryptionKeyCached , resolve ) ;
2400+ } ) ;
2401+ const keyBackupEnabledPromise = new Promise < void > ( ( resolve ) => {
2402+ aliceClient . on ( CryptoEvent . KeyBackupStatus , ( enabled ) => {
2403+ if ( enabled ) {
2404+ resolve ( ) ;
2405+ }
2406+ } ) ;
2407+ } ) ;
2408+
2409+ syncResponder . sendOrQueueSyncResponse ( syncResponse ) ;
2410+ await syncPromise ( aliceClient ) ;
2411+
2412+ // alice should be using backup now
2413+ expect ( await backupKeyReceivedPromise ) . toBe ( testData . SIGNED_BACKUP_DATA . version ) ;
2414+ await keyBackupEnabledPromise ;
2415+ expect ( await crypto . getActiveSessionBackupVersion ( ) ) . toBe ( testData . SIGNED_BACKUP_DATA . version ) ;
2416+ } ) ;
2417+ } ) ;
23142418} ) ;
0 commit comments