@@ -18,12 +18,14 @@ import fetchMock from "fetch-mock-jest";
1818import { MockResponse } from "fetch-mock" ;
1919
2020import { createClient , MatrixClient } from "../../../src" ;
21- import { ShowSasCallbacks , VerifierEvent } from "../../../src/crypto-api/verification" ;
21+ import { ShowQrCodeCallbacks , ShowSasCallbacks , VerifierEvent } from "../../../src/crypto-api/verification" ;
2222import { escapeRegExp } from "../../../src/utils" ;
2323import { VerificationBase } from "../../../src/crypto/verification/Base" ;
2424import { CRYPTO_BACKENDS , InitCrypto } from "../../test-utils/test-utils" ;
2525import { SyncResponder } from "../../test-utils/SyncResponder" ;
2626import {
27+ MASTER_CROSS_SIGNING_PUBLIC_KEY_BASE64 ,
28+ SIGNED_CROSS_SIGNING_KEYS_DATA ,
2729 SIGNED_TEST_DEVICE_DATA ,
2830 TEST_DEVICE_ID ,
2931 TEST_DEVICE_PUBLIC_ED25519_KEY_BASE64 ,
@@ -71,6 +73,16 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
7173 } ) ;
7274
7375 await initCrypto ( aliceClient ) ;
76+
77+ // stub out global.crypto
78+ Object . defineProperty ( globalThis , "crypto" , {
79+ value : {
80+ getRandomValues : function < T extends Uint8Array > ( array : T ) : T {
81+ array . fill ( 0x12 ) ;
82+ return array ;
83+ } ,
84+ } ,
85+ } ) ;
7486 } ) ;
7587
7688 afterEach ( async ( ) => {
@@ -208,6 +220,110 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("verification (%s)", (backend: st
208220 olmSAS . free ( ) ;
209221 } ) ;
210222
223+ oldBackendOnly (
224+ "Outgoing verification: can verify another device via QR code with an untrusted cross-signing key" ,
225+ async ( ) => {
226+ // we need to have bootstrapped cross-signing for this
227+ //await bootstrapCrossSigning(aliceClient);
228+ // console.warn("Bootstrapped");
229+
230+ // expect requests to download our own keys
231+ fetchMock . post ( new RegExp ( "/_matrix/client/(r0|v3)/keys/query" ) , {
232+ device_keys : {
233+ [ TEST_USER_ID ] : {
234+ [ TEST_DEVICE_ID ] : SIGNED_TEST_DEVICE_DATA ,
235+ } ,
236+ } ,
237+ ...SIGNED_CROSS_SIGNING_KEYS_DATA ,
238+ } ) ;
239+
240+ // QRCode fails if we don't yet have the cross-signing keys, so make sure we have them now.
241+ //
242+ // Completing the initial sync will make the device list download outdated device lists (of which our own
243+ // user will be one).
244+ syncResponder . sendOrQueueSyncResponse ( { } ) ;
245+ // DeviceList has a sleep(5) which we need to make happen
246+ await jest . advanceTimersByTimeAsync ( 10 ) ;
247+ expect ( aliceClient . getStoredCrossSigningForUser ( TEST_USER_ID ) ) . toBeTruthy ( ) ;
248+
249+ // have alice initiate a verification. She should send a m.key.verification.request
250+ const [ requestBody , request ] = await Promise . all ( [
251+ expectSendToDeviceMessage ( "m.key.verification.request" ) ,
252+ aliceClient . requestVerification ( TEST_USER_ID , [ TEST_DEVICE_ID ] ) ,
253+ ] ) ;
254+ const transactionId = request . channel . transactionId ;
255+
256+ const toDeviceMessage = requestBody . messages [ TEST_USER_ID ] [ TEST_DEVICE_ID ] ;
257+ expect ( toDeviceMessage . methods ) . toContain ( "m.qr_code.show.v1" ) ;
258+ expect ( toDeviceMessage . methods ) . toContain ( "m.qr_code.scan.v1" ) ;
259+ expect ( toDeviceMessage . methods ) . toContain ( "m.reciprocate.v1" ) ;
260+ expect ( toDeviceMessage . from_device ) . toEqual ( aliceClient . deviceId ) ;
261+ expect ( toDeviceMessage . transaction_id ) . toEqual ( transactionId ) ;
262+
263+ // The dummy device replies with an m.key.verification.ready, with an indication we can scan the QR code
264+ returnToDeviceMessageFromSync ( {
265+ type : "m.key.verification.ready" ,
266+ content : {
267+ from_device : TEST_DEVICE_ID ,
268+ methods : [ "m.qr_code.scan.v1" ] ,
269+ transaction_id : transactionId ,
270+ } ,
271+ } ) ;
272+ await waitForVerificationRequestChanged ( request ) ;
273+ expect ( request . phase ) . toEqual ( Phase . Ready ) ;
274+
275+ // we should now have QR data we can display
276+ const qrCodeData = request . qrCodeData ! ;
277+ expect ( qrCodeData ) . toBeTruthy ( ) ;
278+ const qrCodeBuffer = qrCodeData . getBuffer ( ) ;
279+ // https://spec.matrix.org/v1.7/client-server-api/#qr-code-format
280+ expect ( qrCodeBuffer . subarray ( 0 , 6 ) . toString ( "latin1" ) ) . toEqual ( "MATRIX" ) ;
281+ expect ( qrCodeBuffer . readUint8 ( 6 ) ) . toEqual ( 0x02 ) ; // version
282+ expect ( qrCodeBuffer . readUint8 ( 7 ) ) . toEqual ( 0x02 ) ; // mode
283+ const txnIdLen = qrCodeBuffer . readUint16BE ( 8 ) ;
284+ expect ( qrCodeBuffer . subarray ( 10 , 10 + txnIdLen ) . toString ( "utf-8" ) ) . toEqual ( transactionId ) ;
285+ // const aliceDevicePubKey = qrCodeBuffer.subarray(10 + txnIdLen, 32 + 10 + txnIdLen);
286+ expect ( qrCodeBuffer . subarray ( 42 + txnIdLen , 32 + 42 + txnIdLen ) ) . toEqual (
287+ Buffer . from ( MASTER_CROSS_SIGNING_PUBLIC_KEY_BASE64 , "base64" ) ,
288+ ) ;
289+ const sharedSecret = qrCodeBuffer . subarray ( 74 + txnIdLen ) ;
290+
291+ // the dummy device "scans" the displayed QR code and acknowledges it with a "m.key.verification.start"
292+ returnToDeviceMessageFromSync ( {
293+ type : "m.key.verification.start" ,
294+ content : {
295+ from_device : TEST_DEVICE_ID ,
296+ method : "m.reciprocate.v1" ,
297+ transaction_id : transactionId ,
298+ secret : encodeUnpaddedBase64 ( sharedSecret ) ,
299+ } ,
300+ } ) ;
301+ await waitForVerificationRequestChanged ( request ) ;
302+ expect ( request . phase ) . toEqual ( Phase . Started ) ;
303+ expect ( request . chosenMethod ) . toEqual ( "m.reciprocate.v1" ) ;
304+
305+ // there should now be a verifier
306+ const verifier : VerificationBase = request . verifier ! ;
307+ expect ( verifier ) . toBeDefined ( ) ;
308+
309+ // ... which we call .verify on, which emits a ShowReciprocateQr event
310+ const verificationPromise = verifier . verify ( ) ;
311+ const reciprocateQRCode = await new Promise < ShowQrCodeCallbacks > ( ( resolve ) => {
312+ verifier . once ( VerifierEvent . ShowReciprocateQr , resolve ) ;
313+ } ) ;
314+
315+ // Alice confirms she is happy
316+ reciprocateQRCode . confirm ( ) ;
317+
318+ // that should satisfy Alice, who should reply with a 'done'
319+ await expectSendToDeviceMessage ( "m.key.verification.done" ) ;
320+
321+ // ... and the whole thing should be done!
322+ await verificationPromise ;
323+ expect ( request . phase ) . toEqual ( Phase . Done ) ;
324+ } ,
325+ ) ;
326+
211327 function returnToDeviceMessageFromSync ( ev : { type : string ; content : object ; sender ?: string } ) : void {
212328 ev . sender ??= TEST_USER_ID ;
213329 syncResponder . sendOrQueueSyncResponse ( { to_device : { events : [ ev ] } } ) ;
@@ -253,3 +369,7 @@ function calculateMAC(olmSAS: Olm.SAS, input: string, info: string): string {
253369 //console.info(`Test MAC: input:'${input}, info: '${info}' -> '${mac}`);
254370 return mac ;
255371}
372+
373+ function encodeUnpaddedBase64 ( uint8Array : ArrayBuffer | Uint8Array ) : string {
374+ return Buffer . from ( uint8Array ) . toString ( "base64" ) . replace ( / = + $ / g, "" ) ;
375+ }
0 commit comments