@@ -20,7 +20,7 @@ import { MockResponse } from "fetch-mock";
20
20
import fetchMock from "fetch-mock-jest" ;
21
21
import { IDBFactory } from "fake-indexeddb" ;
22
22
23
- import { createClient , CryptoEvent , MatrixClient } from "../../../src" ;
23
+ import { createClient , CryptoEvent , ICreateClientOpts , MatrixClient } from "../../../src" ;
24
24
import {
25
25
canAcceptVerificationRequest ,
26
26
ShowQrCodeCallbacks ,
@@ -88,6 +88,9 @@ afterAll(() => {
88
88
}
89
89
} ) ;
90
90
91
+ /** The homeserver url that we give to the test client, and where we intercept /sync, /keys, etc requests. */
92
+ const TEST_HOMESERVER_URL = "https://alice-server.com" ;
93
+
91
94
/**
92
95
* Integration tests for verification functionality.
93
96
*
@@ -96,59 +99,39 @@ afterAll(() => {
96
99
*/
97
100
// we test with both crypto stacks...
98
101
describe . each ( Object . entries ( CRYPTO_BACKENDS ) ) ( "verification (%s)" , ( backend : string , initCrypto : InitCrypto ) => {
99
- // and with (1) the default verification method list, (2) a custom verification method list.
100
- describe . each ( [ undefined , [ "m.sas.v1" , "m.qr_code.show.v1" , "m.reciprocate.v1" ] ] ) (
101
- "supported methods=%s" ,
102
- ( methods ) => {
103
- runTests ( backend , initCrypto , methods ) ;
104
- } ,
105
- ) ;
106
- } ) ;
107
-
108
- function runTests ( backend : string , initCrypto : InitCrypto , methods : string [ ] | undefined ) {
109
102
// oldBackendOnly is an alternative to `it` or `test` which will skip the test if we are running against the
110
103
// Rust backend. Once we have full support in the rust sdk, it will go away.
111
104
const oldBackendOnly = backend === "rust-sdk" ? test . skip : test ;
112
105
113
106
/** the client under test */
114
107
let aliceClient : MatrixClient ;
115
108
116
- /** an object which intercepts `/sync` requests from { @link #aliceClient} */
109
+ /** an object which intercepts `/sync` requests on the test homeserver */
117
110
let syncResponder : SyncResponder ;
118
111
119
- /** an object which intercepts `/keys/query` requests from { @link #aliceClient} */
112
+ /** an object which intercepts `/keys/query` requests on the test homeserver */
120
113
let e2eKeyResponder : E2EKeyResponder ;
121
114
122
- /** an object which intercepts `/keys/upload` requests from { @link #aliceClient} */
115
+ /** an object which intercepts `/keys/upload` requests on the test homeserver */
123
116
let e2eKeyReceiver : E2EKeyReceiver ;
124
117
125
118
beforeEach ( async ( ) => {
126
119
// anything that we don't have a specific matcher for silently returns a 404
127
120
fetchMock . catch ( 404 ) ;
128
121
fetchMock . config . warnOnFallback = false ;
129
122
130
- const homeserverUrl = "https://alice-server.com" ;
131
- aliceClient = createClient ( {
132
- baseUrl : homeserverUrl ,
133
- userId : TEST_USER_ID ,
134
- accessToken : "akjgkrgjs" ,
135
- deviceId : "device_under_test" ,
136
- verificationMethods : methods ,
137
- } ) ;
138
-
139
- await initCrypto ( aliceClient ) ;
140
-
141
- e2eKeyReceiver = new E2EKeyReceiver ( aliceClient . getHomeserverUrl ( ) ) ;
142
- e2eKeyResponder = new E2EKeyResponder ( aliceClient . getHomeserverUrl ( ) ) ;
123
+ e2eKeyReceiver = new E2EKeyReceiver ( TEST_HOMESERVER_URL ) ;
124
+ e2eKeyResponder = new E2EKeyResponder ( TEST_HOMESERVER_URL ) ;
143
125
e2eKeyResponder . addKeyReceiver ( TEST_USER_ID , e2eKeyReceiver ) ;
126
+ syncResponder = new SyncResponder ( TEST_HOMESERVER_URL ) ;
144
127
145
- syncResponder = new SyncResponder ( aliceClient . getHomeserverUrl ( ) ) ;
146
- mockInitialApiRequests ( aliceClient . getHomeserverUrl ( ) ) ;
147
- await aliceClient . startClient ( ) ;
128
+ mockInitialApiRequests ( TEST_HOMESERVER_URL ) ;
148
129
} ) ;
149
130
150
131
afterEach ( async ( ) => {
151
- await aliceClient . stopClient ( ) ;
132
+ if ( aliceClient !== undefined ) {
133
+ await aliceClient . stopClient ( ) ;
134
+ }
152
135
153
136
// Allow in-flight things to complete before we tear down the test
154
137
await jest . runAllTimersAsync ( ) ;
@@ -162,7 +145,10 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
162
145
e2eKeyResponder . addDeviceKeys ( TEST_USER_ID , TEST_DEVICE_ID , SIGNED_TEST_DEVICE_DATA ) ;
163
146
} ) ;
164
147
165
- it ( "can verify another device via SAS" , async ( ) => {
148
+ // test with (1) the default verification method list, (2) a custom verification method list.
149
+ const TEST_METHODS = [ "m.sas.v1" , "m.qr_code.show.v1" , "m.reciprocate.v1" ] ;
150
+ it . each ( [ undefined , TEST_METHODS ] ) ( "can verify via SAS (supported methods=%s)" , async ( methods ) => {
151
+ aliceClient = await startTestClient ( { verificationMethods : methods } ) ;
166
152
await waitForDeviceList ( ) ;
167
153
168
154
// initially there should be no verifications in progress
@@ -176,7 +162,7 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
176
162
expectSendToDeviceMessage ( "m.key.verification.request" ) ,
177
163
aliceClient . getCrypto ( ) ! . requestDeviceVerification ( TEST_USER_ID , TEST_DEVICE_ID ) ,
178
164
] ) ;
179
- const transactionId = request . transactionId ;
165
+ const transactionId = request . transactionId ! ;
180
166
expect ( transactionId ) . toBeDefined ( ) ;
181
167
expect ( request . phase ) . toEqual ( VerificationPhase . Requested ) ;
182
168
expect ( request . roomId ) . toBeUndefined ( ) ;
@@ -202,32 +188,14 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
202
188
}
203
189
204
190
// The dummy device replies with an m.key.verification.ready...
205
- returnToDeviceMessageFromSync ( {
206
- type : "m.key.verification.ready" ,
207
- content : {
208
- from_device : TEST_DEVICE_ID ,
209
- methods : [ "m.sas.v1" ] ,
210
- transaction_id : transactionId ,
211
- } ,
212
- } ) ;
191
+ returnToDeviceMessageFromSync ( buildReadyMessage ( transactionId , [ "m.sas.v1" ] ) ) ;
213
192
await waitForVerificationRequestChanged ( request ) ;
214
193
expect ( request . phase ) . toEqual ( VerificationPhase . Ready ) ;
215
194
expect ( request . otherDeviceId ) . toEqual ( TEST_DEVICE_ID ) ;
216
195
217
196
// ... and picks a method with m.key.verification.start
218
- returnToDeviceMessageFromSync ( {
219
- type : "m.key.verification.start" ,
220
- content : {
221
- from_device : TEST_DEVICE_ID ,
222
- method : "m.sas.v1" ,
223
- transaction_id : transactionId ,
224
- hashes : [ "sha256" ] ,
225
- key_agreement_protocols : [ "curve25519-hkdf-sha256" ] ,
226
- message_authentication_codes : [ "hkdf-hmac-sha256.v2" ] ,
227
- // we have to include "decimal" per the spec.
228
- short_authentication_string : [ "decimal" , "emoji" ] ,
229
- } ,
230
- } ) ;
197
+ returnToDeviceMessageFromSync ( buildSasStartMessage ( transactionId ) ) ;
198
+
231
199
// as soon as the Changed event arrives, `verifier` should be defined
232
200
const verifier = await new Promise < Verifier > ( ( resolve ) => {
233
201
function onChange ( ) {
@@ -327,6 +295,7 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
327
295
} ) ;
328
296
329
297
it ( "Can make a verification request to *all* devices" , async ( ) => {
298
+ aliceClient = await startTestClient ( ) ;
330
299
// we need an existing cross-signing key for this
331
300
e2eKeyResponder . addCrossSigningData ( SIGNED_CROSS_SIGNING_KEYS_DATA ) ;
332
301
await waitForDeviceList ( ) ;
@@ -356,6 +325,7 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
356
325
} ) ;
357
326
358
327
oldBackendOnly ( "can verify another via QR code with an untrusted cross-signing key" , async ( ) => {
328
+ aliceClient = await startTestClient ( ) ;
359
329
// QRCode fails if we don't yet have the cross-signing keys, so make sure we have them now.
360
330
e2eKeyResponder . addCrossSigningData ( SIGNED_CROSS_SIGNING_KEYS_DATA ) ;
361
331
await waitForDeviceList ( ) ;
@@ -366,26 +336,17 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
366
336
expectSendToDeviceMessage ( "m.key.verification.request" ) ,
367
337
aliceClient . getCrypto ( ) ! . requestDeviceVerification ( TEST_USER_ID , TEST_DEVICE_ID ) ,
368
338
] ) ;
369
- const transactionId = request . transactionId ;
339
+ const transactionId = request . transactionId ! ;
370
340
371
341
const toDeviceMessage = requestBody . messages [ TEST_USER_ID ] [ TEST_DEVICE_ID ] ;
372
342
expect ( toDeviceMessage . methods ) . toContain ( "m.qr_code.show.v1" ) ;
373
343
expect ( toDeviceMessage . methods ) . toContain ( "m.reciprocate.v1" ) ;
374
- if ( methods === undefined ) {
375
- expect ( toDeviceMessage . methods ) . toContain ( "m.qr_code.scan.v1" ) ;
376
- }
344
+ expect ( toDeviceMessage . methods ) . toContain ( "m.qr_code.scan.v1" ) ;
377
345
expect ( toDeviceMessage . from_device ) . toEqual ( aliceClient . deviceId ) ;
378
346
expect ( toDeviceMessage . transaction_id ) . toEqual ( transactionId ) ;
379
347
380
348
// The dummy device replies with an m.key.verification.ready, with an indication we can scan the QR code
381
- returnToDeviceMessageFromSync ( {
382
- type : "m.key.verification.ready" ,
383
- content : {
384
- from_device : TEST_DEVICE_ID ,
385
- methods : [ "m.qr_code.scan.v1" ] ,
386
- transaction_id : transactionId ,
387
- } ,
388
- } ) ;
349
+ returnToDeviceMessageFromSync ( buildReadyMessage ( transactionId , [ "m.qr_code.scan.v1" ] ) ) ;
389
350
await waitForVerificationRequestChanged ( request ) ;
390
351
expect ( request . phase ) . toEqual ( VerificationPhase . Ready ) ;
391
352
@@ -447,40 +408,22 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
447
408
} ) ;
448
409
449
410
it ( "can cancel during the SAS phase" , async ( ) => {
411
+ aliceClient = await startTestClient ( ) ;
450
412
await waitForDeviceList ( ) ;
451
413
452
414
// have alice initiate a verification. She should send a m.key.verification.request
453
415
const [ , request ] = await Promise . all ( [
454
416
expectSendToDeviceMessage ( "m.key.verification.request" ) ,
455
417
aliceClient . getCrypto ( ) ! . requestDeviceVerification ( TEST_USER_ID , TEST_DEVICE_ID ) ,
456
418
] ) ;
457
- const transactionId = request . transactionId ;
419
+ const transactionId = request . transactionId ! ;
458
420
459
421
// The dummy device replies with an m.key.verification.ready...
460
- returnToDeviceMessageFromSync ( {
461
- type : "m.key.verification.ready" ,
462
- content : {
463
- from_device : TEST_DEVICE_ID ,
464
- methods : [ "m.sas.v1" ] ,
465
- transaction_id : transactionId ,
466
- } ,
467
- } ) ;
422
+ returnToDeviceMessageFromSync ( buildReadyMessage ( transactionId , [ "m.sas.v1" ] ) ) ;
468
423
await waitForVerificationRequestChanged ( request ) ;
469
424
470
425
// ... and picks a method with m.key.verification.start
471
- returnToDeviceMessageFromSync ( {
472
- type : "m.key.verification.start" ,
473
- content : {
474
- from_device : TEST_DEVICE_ID ,
475
- method : "m.sas.v1" ,
476
- transaction_id : transactionId ,
477
- hashes : [ "sha256" ] ,
478
- key_agreement_protocols : [ "curve25519-hkdf-sha256" ] ,
479
- message_authentication_codes : [ "hkdf-hmac-sha256.v2" ] ,
480
- // we have to include "decimal" per the spec.
481
- short_authentication_string : [ "decimal" , "emoji" ] ,
482
- } ,
483
- } ) ;
426
+ returnToDeviceMessageFromSync ( buildSasStartMessage ( transactionId ) ) ;
484
427
await waitForVerificationRequestChanged ( request ) ;
485
428
expect ( request . phase ) . toEqual ( VerificationPhase . Started ) ;
486
429
@@ -514,6 +457,7 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
514
457
} ) ;
515
458
516
459
oldBackendOnly ( "Incoming verification: can accept" , async ( ) => {
460
+ aliceClient = await startTestClient ( ) ;
517
461
const TRANSACTION_ID = "abcd" ;
518
462
519
463
// Initiate the request by sending a to-device message
@@ -551,6 +495,19 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
551
495
} ) ;
552
496
} ) ;
553
497
498
+ async function startTestClient ( opts : Partial < ICreateClientOpts > = { } ) : Promise < MatrixClient > {
499
+ const client = createClient ( {
500
+ baseUrl : TEST_HOMESERVER_URL ,
501
+ userId : TEST_USER_ID ,
502
+ accessToken : "akjgkrgjs" ,
503
+ deviceId : "device_under_test" ,
504
+ ...opts ,
505
+ } ) ;
506
+ await initCrypto ( client ) ;
507
+ await client . startClient ( ) ;
508
+ return client ;
509
+ }
510
+
554
511
/** make sure that the client knows about the dummy device */
555
512
async function waitForDeviceList ( ) : Promise < void > {
556
513
// Completing the initial sync will make the device list download outdated device lists (of which our own
@@ -568,7 +525,7 @@ function runTests(backend: string, initCrypto: InitCrypto, methods: string[] | u
568
525
ev . sender ??= TEST_USER_ID ;
569
526
syncResponder . sendOrQueueSyncResponse ( { to_device : { events : [ ev ] } } ) ;
570
527
}
571
- }
528
+ } ) ;
572
529
573
530
/**
574
531
* Wait for the client under test to send a to-device message of the given type.
@@ -613,3 +570,32 @@ function calculateMAC(olmSAS: Olm.SAS, input: string, info: string): string {
613
570
function encodeUnpaddedBase64 ( uint8Array : ArrayBuffer | Uint8Array ) : string {
614
571
return Buffer . from ( uint8Array ) . toString ( "base64" ) . replace ( / = + $ / g, "" ) ;
615
572
}
573
+
574
+ /** build an m.key.verification.ready to-device message originating from the dummy device */
575
+ function buildReadyMessage ( transactionId : string , methods : string [ ] ) : { type : string ; content : object } {
576
+ return {
577
+ type : "m.key.verification.ready" ,
578
+ content : {
579
+ from_device : TEST_DEVICE_ID ,
580
+ methods : methods ,
581
+ transaction_id : transactionId ,
582
+ } ,
583
+ } ;
584
+ }
585
+
586
+ /** build an m.key.verification.start to-device message suitable for the SAS flow, originating from the dummy device */
587
+ function buildSasStartMessage ( transactionId : string ) : { type : string ; content : object } {
588
+ return {
589
+ type : "m.key.verification.start" ,
590
+ content : {
591
+ from_device : TEST_DEVICE_ID ,
592
+ method : "m.sas.v1" ,
593
+ transaction_id : transactionId ,
594
+ hashes : [ "sha256" ] ,
595
+ key_agreement_protocols : [ "curve25519-hkdf-sha256" ] ,
596
+ message_authentication_codes : [ "hkdf-hmac-sha256.v2" ] ,
597
+ // we have to include "decimal" per the spec.
598
+ short_authentication_string : [ "decimal" , "emoji" ] ,
599
+ } ,
600
+ } ;
601
+ }
0 commit comments