@@ -19,7 +19,14 @@ import type { VerificationRequest } from "matrix-js-sdk/src/crypto-api";
1919import type { CypressBot } from "../../support/bot" ;
2020import { HomeserverInstance } from "../../plugins/utils/homeserver" ;
2121import { UserCredentials } from "../../support/login" ;
22- import { doTwoWaySasVerification , waitForVerificationRequest } from "./utils" ;
22+ import {
23+ doTwoWaySasVerification ,
24+ downloadKey ,
25+ enableKeyBackup ,
26+ logIntoElement ,
27+ logOutOfElement ,
28+ waitForVerificationRequest ,
29+ } from "./utils" ;
2330import { skipIfRustCrypto } from "../../support/util" ;
2431
2532interface CryptoTestContext extends Mocha . Context {
@@ -129,19 +136,26 @@ const verify = function (this: CryptoTestContext) {
129136
130137describe ( "Cryptography" , function ( ) {
131138 let aliceCredentials : UserCredentials ;
139+ let homeserver : HomeserverInstance ;
140+ let bob : CypressBot ;
132141
133142 beforeEach ( function ( ) {
134143 cy . startHomeserver ( "default" )
135144 . as ( "homeserver" )
136- . then ( ( homeserver : HomeserverInstance ) => {
145+ . then ( ( data ) => {
146+ homeserver = data ;
137147 cy . initTestUser ( homeserver , "Alice" , undefined , "alice_" ) . then ( ( credentials ) => {
138148 aliceCredentials = credentials ;
139149 } ) ;
140- cy . getBot ( homeserver , {
150+ return cy . getBot ( homeserver , {
141151 displayName : "Bob" ,
142152 autoAcceptInvites : false ,
143153 userIdPrefix : "bob_" ,
144- } ) . as ( "bob" ) ;
154+ } ) ;
155+ } )
156+ . as ( "bob" )
157+ . then ( ( data ) => {
158+ bob = data ;
145159 } ) ;
146160 } ) ;
147161
@@ -169,15 +183,6 @@ describe("Cryptography", function () {
169183 } ) ;
170184 }
171185
172- /**
173- * Click on download button and continue
174- */
175- function downloadKey ( ) {
176- // Clicking download instead of Copy because of https://github.com/cypress-io/cypress/issues/2851
177- cy . findByRole ( "button" , { name : "Download" } ) . click ( ) ;
178- cy . contains ( ".mx_Dialog_primary:not([disabled])" , "Continue" ) . click ( ) ;
179- }
180-
181186 it ( "by recovery code" , ( ) => {
182187 skipIfRustCrypto ( ) ;
183188
@@ -294,89 +299,232 @@ describe("Cryptography", function () {
294299 verify . call ( this ) ;
295300 } ) ;
296301
297- it ( "should show the correct shield on edited e2e events" , function ( this : CryptoTestContext ) {
298- skipIfRustCrypto ( ) ;
299- cy . bootstrapCrossSigning ( aliceCredentials ) ;
302+ describe ( "event shields" , ( ) => {
303+ let testRoomId : string ;
300304
301- // bob has a second, not cross-signed, device
302- cy . loginBot ( this . homeserver , this . bob . getUserId ( ) , this . bob . __cypress_password , { } ) . as ( "bobSecondDevice" ) ;
305+ beforeEach ( ( ) => {
306+ cy . bootstrapCrossSigning ( aliceCredentials ) ;
307+ autoJoin ( bob ) ;
303308
304- autoJoin ( this . bob ) ;
309+ // create an encrypted room
310+ cy . createRoom ( { name : "TestRoom" , invite : [ bob . getUserId ( ) ] } )
311+ . as ( "testRoomId" )
312+ . then ( ( roomId ) => {
313+ testRoomId = roomId ;
314+ cy . log ( `Created test room ${ roomId } ` ) ;
315+ cy . visit ( `/#/room/${ roomId } ` ) ;
305316
306- // first create the room, so that we can open the verification panel
307- cy . createRoom ( { name : "TestRoom" , invite : [ this . bob . getUserId ( ) ] } )
308- . as ( "testRoomId" )
309- . then ( ( roomId ) => {
310- cy . log ( `Created test room ${ roomId } ` ) ;
311- cy . visit ( `/#/room/${ roomId } ` ) ;
317+ // enable encryption
318+ cy . getClient ( ) . then ( ( cli ) => {
319+ cli . sendStateEvent ( roomId , "m.room.encryption" , { algorithm : "m.megolm.v1.aes-sha2" } ) ;
320+ } ) ;
312321
313- // enable encryption
314- cy . getClient ( ) . then ( ( cli ) => {
315- cli . sendStateEvent ( roomId , "m.room.encryption" , { algorithm : "m.megolm.v1.aes-sha2" } ) ;
322+ // wait for Bob to join the room, otherwise our attempt to open his user details may race
323+ // with his join.
324+ cy . findByText ( "Bob joined the room" ) . should ( "exist" ) ;
316325 } ) ;
326+ } ) ;
327+
328+ it ( "should show the correct shield on e2e events" , function ( this : CryptoTestContext ) {
329+ skipIfRustCrypto ( ) ;
317330
318- // wait for Bob to join the room, otherwise our attempt to open his user details may race
319- // with his join.
320- cy . findByText ( "Bob joined the room" ) . should ( "exist" ) ;
331+ // Bob has a second, not cross-signed, device
332+ let bobSecondDevice : MatrixClient ;
333+ cy . loginBot ( homeserver , bob . getUserId ( ) , bob . __cypress_password , { } ) . then ( async ( data ) => {
334+ bobSecondDevice = data ;
321335 } ) ;
322336
323- verify . call ( this ) ;
337+ /* Should show an error for a decryption failure */
338+ cy . wrap ( 0 ) . then ( ( ) =>
339+ bob . sendEvent ( testRoomId , "m.room.encrypted" , {
340+ algorithm : "m.megolm.v1.aes-sha2" ,
341+ ciphertext : "the bird is in the hand" ,
342+ } ) ,
343+ ) ;
344+
345+ cy . get ( ".mx_EventTile_last" )
346+ . should ( "contain" , "Unable to decrypt message" )
347+ . find ( ".mx_EventTile_e2eIcon" )
348+ . should ( "have.class" , "mx_EventTile_e2eIcon_decryption_failure" )
349+ . should ( "have.attr" , "aria-label" , "This message could not be decrypted" ) ;
350+
351+ /* Should show a red padlock for an unencrypted message in an e2e room */
352+ cy . wrap ( 0 )
353+ . then ( ( ) =>
354+ bob . http . authedRequest < ISendEventResponse > (
355+ // @ts -ignore-next this wants a Method instance, but that is hard to get to here
356+ "PUT" ,
357+ `/rooms/${ encodeURIComponent ( testRoomId ) } /send/m.room.message/test_txn_1` ,
358+ undefined ,
359+ {
360+ msgtype : "m.text" ,
361+ body : "test unencrypted" ,
362+ } ,
363+ ) ,
364+ )
365+ . then ( ( resp ) => cy . log ( `Bob sent unencrypted event with event id ${ resp . event_id } ` ) ) ;
324366
325- cy . get < string > ( "@testRoomId" ) . then ( ( roomId ) => {
367+ cy . get ( ".mx_EventTile_last" )
368+ . should ( "contain" , "test unencrypted" )
369+ . find ( ".mx_EventTile_e2eIcon" )
370+ . should ( "have.class" , "mx_EventTile_e2eIcon_warning" )
371+ . should ( "have.attr" , "aria-label" , "Unencrypted" ) ;
372+
373+ /* Should show no padlock for an unverified user */
326374 // bob sends a valid event
327- cy . wrap ( this . bob . sendTextMessage ( roomId , "Hoo!" ) ) . as ( "testEvent" ) ;
328-
329- // the message should appear, decrypted, with no warning
330- cy . get ( ".mx_EventTile_last .mx_EventTile_body" )
331- . within ( ( ) => {
332- cy . findByText ( "Hoo!" ) ;
333- } )
334- . closest ( ".mx_EventTile" )
335- . should ( "not.have.descendants" , ".mx_EventTile_e2eIcon_warning" ) ;
336-
337- // bob sends an edit to the first message with his unverified device
338- cy . get < MatrixClient > ( "@bobSecondDevice" ) . then ( ( bobSecondDevice ) => {
375+ cy . wrap ( 0 )
376+ . then ( ( ) => bob . sendTextMessage ( testRoomId , "test encrypted 1" ) )
377+ . then ( ( resp ) => cy . log ( `Bob sent message from primary device with event id ${ resp . event_id } ` ) ) ;
378+
379+ // the message should appear, decrypted, with no warning, but also no "verified"
380+ cy . get ( ".mx_EventTile_last" )
381+ . should ( "contain" , "test encrypted 1" )
382+ // no e2e icon
383+ . should ( "not.have.descendants" , ".mx_EventTile_e2eIcon" ) ;
384+
385+ /* Now verify Bob */
386+ verify . call ( this ) ;
387+
388+ /* Existing message should be updated when user is verified. */
389+ cy . get ( ".mx_EventTile_last" )
390+ . should ( "contain" , "test encrypted 1" )
391+ // still no e2e icon
392+ . should ( "not.have.descendants" , ".mx_EventTile_e2eIcon" ) ;
393+
394+ /* should show no padlock, and be verified, for a message from a verified device */
395+ cy . wrap ( 0 )
396+ . then ( ( ) => bob . sendTextMessage ( testRoomId , "test encrypted 2" ) )
397+ . then ( ( resp ) => cy . log ( `Bob sent second message from primary device with event id ${ resp . event_id } ` ) ) ;
398+
399+ cy . get ( ".mx_EventTile_last" )
400+ . should ( "contain" , "test encrypted 2" )
401+ // no e2e icon
402+ . should ( "not.have.descendants" , ".mx_EventTile_e2eIcon" ) ;
403+
404+ /* should show red padlock for a message from an unverified device */
405+ cy . wrap ( 0 )
406+ . then ( ( ) => bobSecondDevice . sendTextMessage ( testRoomId , "test encrypted from unverified" ) )
407+ . then ( ( resp ) => cy . log ( `Bob sent message from unverified device with event id ${ resp . event_id } ` ) ) ;
408+
409+ cy . get ( ".mx_EventTile_last" )
410+ . should ( "contain" , "test encrypted from unverified" )
411+ . find ( ".mx_EventTile_e2eIcon" , { timeout : 100000 } )
412+ . should ( "have.class" , "mx_EventTile_e2eIcon_warning" )
413+ . should ( "have.attr" , "aria-label" , "Encrypted by an unverified session" ) ;
414+
415+ /* Should show a grey padlock for a message from an unknown device */
416+
417+ // bob deletes his second device, making the encrypted event from the unverified device "unknown".
418+ cy . wrap ( 0 )
419+ . then ( ( ) => bobSecondDevice . logout ( true ) )
420+ . then ( ( ) => cy . log ( `Bob logged out second device` ) ) ;
421+
422+ cy . get ( ".mx_EventTile_last" )
423+ . should ( "contain" , "test encrypted from unverified" )
424+ . find ( ".mx_EventTile_e2eIcon" )
425+ . should ( "have.class" , "mx_EventTile_e2eIcon_normal" )
426+ . should ( "have.attr" , "aria-label" , "Encrypted by a deleted session" ) ;
427+ } ) ;
428+
429+ it ( "Should show a grey padlock for a key restored from backup" , ( ) => {
430+ skipIfRustCrypto ( ) ;
431+
432+ enableKeyBackup ( ) ;
433+
434+ // bob sends a valid event
435+ cy . wrap ( 0 )
436+ . then ( ( ) => bob . sendTextMessage ( testRoomId , "test encrypted 1" ) )
437+ . then ( ( resp ) => cy . log ( `Bob sent message from primary device with event id ${ resp . event_id } ` ) ) ;
438+
439+ cy . get ( ".mx_EventTile_last" )
440+ . should ( "contain" , "test encrypted 1" )
441+ // no e2e icon
442+ . should ( "not.have.descendants" , ".mx_EventTile_e2eIcon" ) ;
443+
444+ /* log out, and back i */
445+ logOutOfElement ( ) ;
446+ cy . get < string > ( "@securityKey" ) . then ( ( securityKey ) => {
447+ logIntoElement ( homeserver . baseUrl , aliceCredentials . username , aliceCredentials . password , securityKey ) ;
448+ } ) ;
449+
450+ /* go back to the test room and find Bob's message again */
451+ cy . viewRoomById ( testRoomId ) ;
452+ cy . get ( ".mx_EventTile_last" )
453+ . should ( "contain" , "test encrypted 1" )
454+ . find ( ".mx_EventTile_e2eIcon" )
455+ . should ( "have.class" , "mx_EventTile_e2eIcon_normal" )
456+ . should (
457+ "have.attr" ,
458+ "aria-label" ,
459+ "The authenticity of this encrypted message can't be guaranteed on this device." ,
460+ ) ;
461+ } ) ;
462+
463+ it ( "should show the correct shield on edited e2e events" , function ( this : CryptoTestContext ) {
464+ skipIfRustCrypto ( ) ;
465+
466+ // bob has a second, not cross-signed, device
467+ cy . loginBot ( this . homeserver , this . bob . getUserId ( ) , this . bob . __cypress_password , { } ) . as ( "bobSecondDevice" ) ;
468+
469+ // verify Bob
470+ verify . call ( this ) ;
471+
472+ cy . get < string > ( "@testRoomId" ) . then ( ( roomId ) => {
473+ // bob sends a valid event
474+ cy . wrap ( this . bob . sendTextMessage ( roomId , "Hoo!" ) ) . as ( "testEvent" ) ;
475+
476+ // the message should appear, decrypted, with no warning
477+ cy . get ( ".mx_EventTile_last .mx_EventTile_body" )
478+ . within ( ( ) => {
479+ cy . findByText ( "Hoo!" ) ;
480+ } )
481+ . closest ( ".mx_EventTile" )
482+ . should ( "not.have.descendants" , ".mx_EventTile_e2eIcon_warning" ) ;
483+
484+ // bob sends an edit to the first message with his unverified device
485+ cy . get < MatrixClient > ( "@bobSecondDevice" ) . then ( ( bobSecondDevice ) => {
486+ cy . get < ISendEventResponse > ( "@testEvent" ) . then ( ( testEvent ) => {
487+ bobSecondDevice . sendMessage ( roomId , {
488+ "m.new_content" : {
489+ msgtype : "m.text" ,
490+ body : "Haa!" ,
491+ } ,
492+ "m.relates_to" : {
493+ rel_type : "m.replace" ,
494+ event_id : testEvent . event_id ,
495+ } ,
496+ } ) ;
497+ } ) ;
498+ } ) ;
499+
500+ // the edit should have a warning
501+ cy . contains ( ".mx_EventTile_body" , "Haa!" )
502+ . closest ( ".mx_EventTile" )
503+ . within ( ( ) => {
504+ cy . get ( ".mx_EventTile_e2eIcon_warning" ) . should ( "exist" ) ;
505+ } ) ;
506+
507+ // a second edit from the verified device should be ok
339508 cy . get < ISendEventResponse > ( "@testEvent" ) . then ( ( testEvent ) => {
340- bobSecondDevice . sendMessage ( roomId , {
509+ this . bob . sendMessage ( roomId , {
341510 "m.new_content" : {
342511 msgtype : "m.text" ,
343- body : "Haa !" ,
512+ body : "Hee !" ,
344513 } ,
345514 "m.relates_to" : {
346515 rel_type : "m.replace" ,
347516 event_id : testEvent . event_id ,
348517 } ,
349518 } ) ;
350519 } ) ;
351- } ) ;
352-
353- // the edit should have a warning
354- cy . contains ( ".mx_EventTile_body" , "Haa!" )
355- . closest ( ".mx_EventTile" )
356- . within ( ( ) => {
357- cy . get ( ".mx_EventTile_e2eIcon_warning" ) . should ( "exist" ) ;
358- } ) ;
359520
360- // a second edit from the verified device should be ok
361- cy . get < ISendEventResponse > ( "@testEvent" ) . then ( ( testEvent ) => {
362- this . bob . sendMessage ( roomId , {
363- "m.new_content" : {
364- msgtype : "m.text" ,
365- body : "Hee!" ,
366- } ,
367- "m.relates_to" : {
368- rel_type : "m.replace" ,
369- event_id : testEvent . event_id ,
370- } ,
371- } ) ;
521+ cy . get ( ".mx_EventTile_last .mx_EventTile_body" )
522+ . within ( ( ) => {
523+ cy . findByText ( "Hee!" ) ;
524+ } )
525+ . closest ( ".mx_EventTile" )
526+ . should ( "not.have.descendants" , ".mx_EventTile_e2eIcon_warning" ) ;
372527 } ) ;
373-
374- cy . get ( ".mx_EventTile_last .mx_EventTile_body" )
375- . within ( ( ) => {
376- cy . findByText ( "Hee!" ) ;
377- } )
378- . closest ( ".mx_EventTile" )
379- . should ( "not.have.descendants" , ".mx_EventTile_e2eIcon_warning" ) ;
380528 } ) ;
381529 } ) ;
382530} ) ;
0 commit comments