972 lines
35 KiB
Dart
972 lines
35 KiB
Dart
/*
|
|
* Famedly Matrix SDK
|
|
* Copyright (C) 2020 Famedly GmbH
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:olm/olm.dart' as olm;
|
|
import 'package:test/test.dart';
|
|
import 'package:vodozemac/vodozemac.dart' as vod;
|
|
|
|
import 'package:matrix/encryption.dart';
|
|
import 'package:matrix/matrix.dart';
|
|
import '../fake_client.dart';
|
|
import '../fake_database.dart';
|
|
|
|
Event getLastSentEvent(KeyVerification req) {
|
|
final entry = FakeMatrixApi.calledEndpoints.entries
|
|
.firstWhere((p) => p.key.contains('/send/'));
|
|
final type = entry.key.split('/')[6];
|
|
final content = json.decode(entry.value.first);
|
|
return Event.fromJson(
|
|
{
|
|
'event_id': req.transactionId,
|
|
'type': type,
|
|
'content': content,
|
|
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
|
'sender': req.client.userID,
|
|
},
|
|
req.room!,
|
|
);
|
|
}
|
|
|
|
void main() async {
|
|
/// All Tests related to the ChatTime
|
|
group('Key Verification', tags: 'olm', () {
|
|
Logs().level = Level.error;
|
|
|
|
// key @othertest:fakeServer.notExisting
|
|
const otherPickledOlmAccount =
|
|
'0aFMkSgJhj0kVLxVnactRpl3L2kgIR8bAqICFtDkvp/mkinITZjr1Vh6Jy9FmJzvhLfFUtjU2j/2bqrFn61CSrvRbRaLP6rCFegGJHNGpVfw+c24NthCwGF/SN10aPjPo6yQ3er9bc42I6AmJz5HgyfU6C4bE+LdWrML93C0iEnmQN/SYHnS1KHPXNl6NpFGITggbZQ9jwHOFILWo8wzJ4iqlJtMrNaOOLAAB7By7Fbxl4xoNz2K+w';
|
|
|
|
late Client client1;
|
|
late Client client2;
|
|
|
|
setUpAll(() async {
|
|
await vod.init(
|
|
wasmPath: './pkg/',
|
|
libraryPath: './rust/target/debug/',
|
|
);
|
|
await olm.init();
|
|
olm.get_library_version();
|
|
});
|
|
|
|
setUp(() async {
|
|
client1 = await getClient();
|
|
client2 = Client(
|
|
'othertestclient',
|
|
httpClient: FakeMatrixApi.currentApi!,
|
|
database: await getDatabase(),
|
|
);
|
|
await client2.checkHomeserver(
|
|
Uri.parse('https://fakeserver.notexisting'),
|
|
checkWellKnown: false,
|
|
);
|
|
await client2.init(
|
|
newToken: 'abc',
|
|
newUserID: '@othertest:fakeServer.notExisting',
|
|
newHomeserver: client2.homeserver,
|
|
newDeviceName: 'Text Matrix Client',
|
|
newDeviceID: 'FOXDEVICE',
|
|
newOlmAccount: otherPickledOlmAccount,
|
|
);
|
|
await Future.delayed(Duration(milliseconds: 10));
|
|
client1.verificationMethods = {
|
|
KeyVerificationMethod.emoji,
|
|
KeyVerificationMethod.numbers,
|
|
KeyVerificationMethod.qrScan,
|
|
KeyVerificationMethod.qrShow,
|
|
KeyVerificationMethod.reciprocate,
|
|
};
|
|
client2.verificationMethods = {
|
|
KeyVerificationMethod.emoji,
|
|
KeyVerificationMethod.numbers,
|
|
KeyVerificationMethod.qrShow,
|
|
KeyVerificationMethod.reciprocate,
|
|
};
|
|
|
|
// cancel sync to reduce background load and prevent sync overwriting which keys are tracked.
|
|
await client1.abortSync();
|
|
await client2.abortSync();
|
|
|
|
// get client2 device keys to start verification
|
|
await client1.updateUserDeviceKeys(additionalUsers: {client2.userID!});
|
|
await client2.updateUserDeviceKeys(additionalUsers: {client1.userID!});
|
|
});
|
|
|
|
tearDown(() async {
|
|
await client1.dispose(closeDatabase: true);
|
|
await client2.dispose(closeDatabase: true);
|
|
});
|
|
|
|
test('Run emoji / number verification', () async {
|
|
// for a full run we test in-room verification in a cleartext room
|
|
// because then we can easily intercept the payloads and inject in the other client
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
// make sure our master key is *not* verified to not triger SSSS for now
|
|
client1.userDeviceKeys[client1.userID]!.masterKey!
|
|
.setDirectVerified(false);
|
|
final req1 =
|
|
await client1.userDeviceKeys[client2.userID]!.startVerification(
|
|
newDirectChatEnableEncryption: false,
|
|
);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
|
|
),
|
|
);
|
|
var evt = getLastSentEvent(req1);
|
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
|
|
|
final comp = Completer<KeyVerification>();
|
|
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
|
comp.complete(req);
|
|
});
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
final req2 = await comp.future;
|
|
await sub.cancel();
|
|
|
|
expect(
|
|
client2.encryption!.keyVerificationManager
|
|
.getRequest(req2.transactionId!),
|
|
req2,
|
|
);
|
|
// send ready
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await req2.acceptVerification();
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.ready',
|
|
),
|
|
);
|
|
|
|
evt = getLastSentEvent(req2);
|
|
expect(req2.state, KeyVerificationState.askChoice);
|
|
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
|
|
expect(req1.possibleMethods, [EventTypes.Sas]);
|
|
expect(req2.possibleMethods, [EventTypes.Sas]);
|
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
|
|
|
// no need for start (continueVerification) because sas only mode override already sent it after ready
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.start',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req1);
|
|
|
|
// send accept
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.accept',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req2);
|
|
|
|
// send key
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req1);
|
|
|
|
// send key
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req2);
|
|
|
|
// receive last key
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
|
|
// compare emoji
|
|
expect(req1.state, KeyVerificationState.askSas);
|
|
expect(req2.state, KeyVerificationState.askSas);
|
|
expect(req1.sasTypes[0], 'emoji');
|
|
expect(req1.sasTypes[1], 'decimal');
|
|
expect(req2.sasTypes[0], 'emoji');
|
|
expect(req2.sasTypes[1], 'decimal');
|
|
// compare emoji
|
|
final emoji1 = req1.sasEmojis;
|
|
final emoji2 = req2.sasEmojis;
|
|
for (var i = 0; i < 7; i++) {
|
|
expect(emoji1[i].emoji, emoji2[i].emoji);
|
|
expect(emoji1[i].name, emoji2[i].name);
|
|
}
|
|
// compare numbers
|
|
final numbers1 = req1.sasNumbers;
|
|
final numbers2 = req2.sasNumbers;
|
|
for (var i = 0; i < 3; i++) {
|
|
expect(numbers1[i], numbers2[i]);
|
|
}
|
|
|
|
// alright, they match
|
|
|
|
// send mac
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await req1.acceptSas();
|
|
evt = getLastSentEvent(req1);
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.mac',
|
|
),
|
|
);
|
|
expect(req1.state, KeyVerificationState.waitingSas);
|
|
|
|
// send mac
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await req2.acceptSas();
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.mac',
|
|
),
|
|
);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.done',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req2);
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.done',
|
|
),
|
|
);
|
|
|
|
expect(req1.state, KeyVerificationState.done);
|
|
expect(req2.state, KeyVerificationState.done);
|
|
expect(
|
|
client1.userDeviceKeys[client2.userID]?.deviceKeys[client2.deviceID]
|
|
?.directVerified,
|
|
true,
|
|
);
|
|
expect(
|
|
client2.userDeviceKeys[client1.userID]?.deviceKeys[client1.deviceID]
|
|
?.directVerified,
|
|
true,
|
|
);
|
|
await client1.encryption!.keyVerificationManager.cleanup();
|
|
await client2.encryption!.keyVerificationManager.cleanup();
|
|
});
|
|
|
|
test('ask SSSS start', () async {
|
|
client1.userDeviceKeys[client1.userID]!.masterKey!
|
|
.setDirectVerified(true);
|
|
await client1.encryption!.ssss.clearCache();
|
|
final req1 = await client1.userDeviceKeys[client2.userID]!
|
|
.startVerification(newDirectChatEnableEncryption: false);
|
|
expect(req1.state, KeyVerificationState.askSSSS);
|
|
await req1.openSSSS(recoveryKey: ssssKey);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
|
|
),
|
|
);
|
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
|
|
|
await req1.cancel();
|
|
await client1.encryption!.keyVerificationManager.cleanup();
|
|
});
|
|
|
|
test('ask SSSS end', () async {
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
// make sure our master key is *not* verified to not triger SSSS for now
|
|
client1.userDeviceKeys[client1.userID]!.masterKey!
|
|
.setDirectVerified(false);
|
|
// the other one has to have their master key verified to trigger asking for ssss
|
|
client2.userDeviceKeys[client2.userID]!.masterKey!
|
|
.setDirectVerified(true);
|
|
final req1 = await client1.userDeviceKeys[client2.userID]!
|
|
.startVerification(newDirectChatEnableEncryption: false);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
|
|
),
|
|
);
|
|
var evt = getLastSentEvent(req1);
|
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
|
|
|
final comp = Completer<KeyVerification>();
|
|
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
|
comp.complete(req);
|
|
});
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
final req2 = await comp.future;
|
|
await sub.cancel();
|
|
|
|
// send ready
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await req2.acceptVerification();
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.ready',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req2);
|
|
expect(req2.state, KeyVerificationState.askChoice);
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
|
|
expect(req1.possibleMethods, [EventTypes.Sas]);
|
|
expect(req2.possibleMethods, [EventTypes.Sas]);
|
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
|
|
|
// no need for start (continueVerification) because sas only mode override already sent it after ready
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.start',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req1);
|
|
|
|
// send accept
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.accept',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req2);
|
|
|
|
// send key
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req1);
|
|
|
|
// send key
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req2);
|
|
|
|
// receive last key
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
|
|
// compare emoji
|
|
expect(req1.state, KeyVerificationState.askSas);
|
|
expect(req2.state, KeyVerificationState.askSas);
|
|
// compare emoji
|
|
final emoji1 = req1.sasEmojis;
|
|
final emoji2 = req2.sasEmojis;
|
|
for (var i = 0; i < 7; i++) {
|
|
expect(emoji1[i].emoji, emoji2[i].emoji);
|
|
expect(emoji1[i].name, emoji2[i].name);
|
|
}
|
|
// compare numbers
|
|
final numbers1 = req1.sasNumbers;
|
|
final numbers2 = req2.sasNumbers;
|
|
for (var i = 0; i < 3; i++) {
|
|
expect(numbers1[i], numbers2[i]);
|
|
}
|
|
|
|
// alright, they match
|
|
client1.userDeviceKeys[client1.userID]!.masterKey!
|
|
.setDirectVerified(true);
|
|
await client1.encryption!.ssss.clearCache();
|
|
|
|
// send mac
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await req1.acceptSas();
|
|
evt = getLastSentEvent(req1);
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.mac',
|
|
),
|
|
);
|
|
expect(req1.state, KeyVerificationState.waitingSas);
|
|
|
|
// send mac
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await req2.acceptSas();
|
|
evt = getLastSentEvent(req2);
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.mac',
|
|
),
|
|
);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.done',
|
|
),
|
|
);
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
|
|
expect(req1.state, KeyVerificationState.askSSSS);
|
|
expect(req2.state, KeyVerificationState.done);
|
|
|
|
await req1.openSSSS(recoveryKey: ssssKey);
|
|
expect(req1.state, KeyVerificationState.done);
|
|
|
|
// let any background box usage from ssss signing finish
|
|
await Future.delayed(Duration(seconds: 1));
|
|
await client1.encryption!.keyVerificationManager.cleanup();
|
|
await client2.encryption!.keyVerificationManager.cleanup();
|
|
});
|
|
|
|
test('reject verification', () async {
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
// make sure our master key is *not* verified to not triger SSSS for now
|
|
client1.userDeviceKeys[client1.userID]!.masterKey!
|
|
.setDirectVerified(false);
|
|
final req1 = await client1.userDeviceKeys[client2.userID]!
|
|
.startVerification(newDirectChatEnableEncryption: false);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
|
|
),
|
|
);
|
|
var evt = getLastSentEvent(req1);
|
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
|
|
|
final comp = Completer<KeyVerification>();
|
|
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
|
comp.complete(req);
|
|
});
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
final req2 = await comp.future;
|
|
await sub.cancel();
|
|
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await req2.rejectVerification();
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.cancel',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req2);
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
expect(req1.state, KeyVerificationState.error);
|
|
expect(req2.state, KeyVerificationState.error);
|
|
|
|
await client1.encryption!.keyVerificationManager.cleanup();
|
|
await client2.encryption!.keyVerificationManager.cleanup();
|
|
});
|
|
|
|
test('reject sas', () async {
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
// make sure our master key is *not* verified to not triger SSSS for now
|
|
client1.userDeviceKeys[client1.userID]!.masterKey!
|
|
.setDirectVerified(false);
|
|
final req1 = await client1.userDeviceKeys[client2.userID]!
|
|
.startVerification(newDirectChatEnableEncryption: false);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
|
|
),
|
|
);
|
|
var evt = getLastSentEvent(req1);
|
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
|
|
|
final comp = Completer<KeyVerification>();
|
|
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
|
comp.complete(req);
|
|
});
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
final req2 = await comp.future;
|
|
await sub.cancel();
|
|
|
|
// send ready
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await req2.acceptVerification();
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.ready',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req2);
|
|
expect(req2.state, KeyVerificationState.askChoice);
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
|
|
expect(req1.possibleMethods, [EventTypes.Sas]);
|
|
expect(req2.possibleMethods, [EventTypes.Sas]);
|
|
|
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
|
|
|
// no need for start (continueVerification) because sas only mode override already sent it after ready
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.start',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req1);
|
|
|
|
// send accept
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.accept',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req2);
|
|
|
|
// send key
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req1);
|
|
|
|
// send key
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.key',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req2);
|
|
|
|
// receive last key
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
|
|
await req1.acceptSas();
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.mac',
|
|
),
|
|
);
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await req2.rejectSas();
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.cancel',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req2);
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
expect(req1.state, KeyVerificationState.error);
|
|
expect(req2.state, KeyVerificationState.error);
|
|
|
|
await client1.encryption!.keyVerificationManager.cleanup();
|
|
await client2.encryption!.keyVerificationManager.cleanup();
|
|
});
|
|
|
|
test('other device accepted', () async {
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
// make sure our master key is *not* verified to not triger SSSS for now
|
|
client1.userDeviceKeys[client1.userID]!.masterKey!
|
|
.setDirectVerified(false);
|
|
final req1 = await client1.userDeviceKeys[client2.userID]!
|
|
.startVerification(newDirectChatEnableEncryption: false);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
|
|
),
|
|
);
|
|
final evt = getLastSentEvent(req1);
|
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
|
|
|
final comp = Completer<KeyVerification>();
|
|
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
|
comp.complete(req);
|
|
});
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
final req2 = await comp.future;
|
|
await sub.cancel();
|
|
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(
|
|
Event.fromJson(
|
|
{
|
|
'event_id': req2.transactionId,
|
|
'type': EventTypes.KeyVerificationReady,
|
|
'content': {
|
|
'methods': [EventTypes.Sas],
|
|
'from_device': 'SOMEOTHERDEVICE',
|
|
'm.relates_to': {
|
|
'rel_type': 'm.reference',
|
|
'event_id': req2.transactionId,
|
|
},
|
|
},
|
|
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
|
'sender': client2.userID,
|
|
},
|
|
req2.room!,
|
|
),
|
|
);
|
|
expect(req2.state, KeyVerificationState.error);
|
|
|
|
await req2.cancel();
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.cancel',
|
|
),
|
|
);
|
|
await client1.encryption!.keyVerificationManager.cleanup();
|
|
await client2.encryption!.keyVerificationManager.cleanup();
|
|
});
|
|
|
|
test('Run qr verification mode 0, ssss start', () async {
|
|
expect(
|
|
client1.userDeviceKeys[client2.userID]?.masterKey!.directVerified,
|
|
false,
|
|
);
|
|
expect(
|
|
client2.userDeviceKeys[client1.userID]?.masterKey!.directVerified,
|
|
false,
|
|
);
|
|
// for a full run we test in-room verification in a cleartext room
|
|
// because then we can easily intercept the payloads and inject in the other client
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
// make sure our master key is *not* verified to not triger SSSS for now
|
|
client1.userDeviceKeys[client1.userID]!.masterKey!
|
|
.setDirectVerified(true);
|
|
client2.userDeviceKeys[client2.userID]!.masterKey!
|
|
.setDirectVerified(true);
|
|
await client1.encryption!.ssss.clearCache();
|
|
|
|
final req1 =
|
|
await client1.userDeviceKeys[client2.userID]!.startVerification(
|
|
newDirectChatEnableEncryption: false,
|
|
);
|
|
|
|
expect(req1.state, KeyVerificationState.askSSSS);
|
|
await req1.openSSSS(recoveryKey: ssssKey);
|
|
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
|
|
),
|
|
);
|
|
|
|
var evt = getLastSentEvent(req1);
|
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
|
|
|
final comp = Completer<KeyVerification>();
|
|
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
|
comp.complete(req);
|
|
});
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
final req2 = await comp.future;
|
|
await sub.cancel();
|
|
|
|
expect(
|
|
client2.encryption!.keyVerificationManager
|
|
.getRequest(req2.transactionId!),
|
|
req2,
|
|
);
|
|
|
|
// send ready
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await req2.acceptVerification();
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.ready',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req2);
|
|
expect(req2.state, KeyVerificationState.askChoice);
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
expect(req1.state, KeyVerificationState.askChoice);
|
|
expect(
|
|
req1.possibleMethods,
|
|
[EventTypes.Sas, EventTypes.Reciprocate, EventTypes.QRScan],
|
|
);
|
|
expect(
|
|
req2.possibleMethods,
|
|
[EventTypes.Sas, EventTypes.Reciprocate, EventTypes.QRShow],
|
|
);
|
|
|
|
// send start
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await req1.continueVerification(
|
|
EventTypes.Reciprocate,
|
|
qrDataRawBytes: Uint8List.fromList(req2.qrCode?.qrDataRawBytes ?? []),
|
|
);
|
|
expect(req2.qrCode!.randomSharedSecret, req1.randomSharedSecretForQRCode);
|
|
expect(req1.state, KeyVerificationState.showQRSuccess);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.start',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req1);
|
|
|
|
// send done
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
expect(req2.state, KeyVerificationState.confirmQRScan);
|
|
await req2.acceptQRScanConfirmation();
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.done',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req2);
|
|
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
|
|
expect(req1.state, KeyVerificationState.done);
|
|
expect(req2.state, KeyVerificationState.done);
|
|
|
|
expect(
|
|
client1.userDeviceKeys[client2.userID]?.masterKey!.directVerified,
|
|
true,
|
|
);
|
|
expect(
|
|
client2.userDeviceKeys[client1.userID]?.masterKey!.directVerified,
|
|
true,
|
|
);
|
|
|
|
await client1.encryption!.keyVerificationManager.cleanup();
|
|
await client2.encryption!.keyVerificationManager.cleanup();
|
|
});
|
|
|
|
test('Run qr verification mode 0, but fail on masterKey unverified client1',
|
|
() async {
|
|
// for a full run we test in-room verification in a cleartext room
|
|
// because then we can easily intercept the payloads and inject in the other client
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
// make sure our master key is *not* verified to not triger SSSS for now
|
|
client1.userDeviceKeys[client1.userID]!.masterKey!
|
|
.setDirectVerified(false);
|
|
|
|
final req1 =
|
|
await client1.userDeviceKeys[client2.userID]!.startVerification(
|
|
newDirectChatEnableEncryption: false,
|
|
);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
|
|
),
|
|
);
|
|
|
|
var evt = getLastSentEvent(req1);
|
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
|
|
|
final comp = Completer<KeyVerification>();
|
|
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
|
comp.complete(req);
|
|
});
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
final req2 = await comp.future;
|
|
await sub.cancel();
|
|
|
|
expect(
|
|
client2.encryption!.keyVerificationManager
|
|
.getRequest(req2.transactionId!),
|
|
req2,
|
|
);
|
|
// send ready
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await req2.acceptVerification();
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.ready',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req2);
|
|
expect(req2.state, KeyVerificationState.askChoice);
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
|
|
expect(req1.possibleMethods, [EventTypes.Sas]);
|
|
expect(req2.possibleMethods, [EventTypes.Sas]);
|
|
|
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
|
|
|
// send start
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
// qrCode will be null here anyway because masterKey not signed
|
|
await req1.continueVerification(
|
|
EventTypes.Reciprocate,
|
|
qrDataRawBytes: Uint8List.fromList(req2.qrCode?.qrDataRawBytes ?? []),
|
|
);
|
|
expect(req1.state, KeyVerificationState.error);
|
|
|
|
await client1.encryption!.keyVerificationManager.cleanup();
|
|
await client2.encryption!.keyVerificationManager.cleanup();
|
|
});
|
|
|
|
test('Run qr verification mode 0, but fail on masterKey unverified client2',
|
|
() async {
|
|
// for a full run we test in-room verification in a cleartext room
|
|
// because then we can easily intercept the payloads and inject in the other client
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
// make sure our master key is *not* verified to not triger SSSS for now
|
|
client1.userDeviceKeys[client1.userID]!.masterKey!
|
|
.setDirectVerified(false);
|
|
|
|
final req1 =
|
|
await client1.userDeviceKeys[client2.userID]!.startVerification(
|
|
newDirectChatEnableEncryption: false,
|
|
);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
|
|
),
|
|
);
|
|
|
|
var evt = getLastSentEvent(req1);
|
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
|
|
|
final comp = Completer<KeyVerification>();
|
|
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
|
comp.complete(req);
|
|
});
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
final req2 = await comp.future;
|
|
await sub.cancel();
|
|
|
|
expect(
|
|
client2.encryption!.keyVerificationManager
|
|
.getRequest(req2.transactionId!),
|
|
req2,
|
|
);
|
|
// send ready
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await req2.acceptVerification();
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.ready',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req2);
|
|
expect(req2.state, KeyVerificationState.askChoice);
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
|
|
expect(req1.possibleMethods, [EventTypes.Sas]);
|
|
expect(req2.possibleMethods, [EventTypes.Sas]);
|
|
|
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
// qrCode will be null here anyway because masterKey not signed
|
|
await req2.continueVerification(
|
|
EventTypes.Reciprocate,
|
|
qrDataRawBytes: Uint8List.fromList(req2.qrCode?.qrDataRawBytes ?? []),
|
|
);
|
|
expect(req2.state, KeyVerificationState.error);
|
|
|
|
await client1.encryption!.keyVerificationManager.cleanup();
|
|
await client2.encryption!.keyVerificationManager.cleanup();
|
|
});
|
|
|
|
test(
|
|
'Run qr verification mode, but fail because no knownVerificationMethod',
|
|
() async {
|
|
client1.verificationMethods = {
|
|
KeyVerificationMethod.emoji,
|
|
KeyVerificationMethod.numbers,
|
|
};
|
|
client2.verificationMethods = {
|
|
KeyVerificationMethod.emoji,
|
|
KeyVerificationMethod.numbers,
|
|
};
|
|
|
|
// for a full run we test in-room verification in a cleartext room
|
|
// because then we can easily intercept the payloads and inject in the other client
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
// make sure our master key is *not* verified to not triger SSSS for now
|
|
client1.userDeviceKeys[client1.userID]!.masterKey!
|
|
.setDirectVerified(false);
|
|
|
|
final req1 =
|
|
await client1.userDeviceKeys[client2.userID]!.startVerification(
|
|
newDirectChatEnableEncryption: false,
|
|
);
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.room.message',
|
|
),
|
|
);
|
|
|
|
var evt = getLastSentEvent(req1);
|
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
|
|
|
final comp = Completer<KeyVerification>();
|
|
final sub = client2.onKeyVerificationRequest.stream.listen((req) {
|
|
comp.complete(req);
|
|
});
|
|
await client2.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
final req2 = await comp.future;
|
|
await sub.cancel();
|
|
|
|
expect(
|
|
client2.encryption!.keyVerificationManager
|
|
.getRequest(req2.transactionId!),
|
|
req2,
|
|
);
|
|
// send ready
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
await req2.acceptVerification();
|
|
await FakeMatrixApi.firstWhere(
|
|
(e) => e.startsWith(
|
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/m.key.verification.ready',
|
|
),
|
|
);
|
|
evt = getLastSentEvent(req2);
|
|
expect(req2.state, KeyVerificationState.askChoice);
|
|
await client1.encryption!.keyVerificationManager.handleEventUpdate(evt);
|
|
|
|
expect(req1.possibleMethods, [EventTypes.Sas]);
|
|
expect(req2.possibleMethods, [EventTypes.Sas]);
|
|
|
|
expect(req1.state, KeyVerificationState.waitingAccept);
|
|
FakeMatrixApi.calledEndpoints.clear();
|
|
|
|
// qrCode will be null here anyway because qr isn't supported
|
|
await req1.continueVerification(
|
|
EventTypes.Reciprocate,
|
|
qrDataRawBytes: Uint8List.fromList(req2.qrCode?.qrDataRawBytes ?? []),
|
|
);
|
|
expect(req1.state, KeyVerificationState.error);
|
|
|
|
await client1.encryption!.keyVerificationManager.cleanup();
|
|
await client2.encryption!.keyVerificationManager.cleanup();
|
|
});
|
|
});
|
|
}
|