diff --git a/lib/src/client.dart b/lib/src/client.dart index cc79c50c..cc1d8431 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -83,6 +83,8 @@ class Client extends MatrixApi { final bool mxidLocalPartFallback; + bool shareKeysWithUnverifiedDevices; + // For CommandsClientExtension final Map Function(CommandArgs)> commands = {}; final Filter syncFilter; @@ -167,6 +169,7 @@ class Client extends MatrixApi { Filter? syncFilter, this.sendTimelineEventTimeout = const Duration(minutes: 1), this.customImageResizer, + this.shareKeysWithUnverifiedDevices = true, }) : syncFilter = syncFilter ?? Filter( room: RoomFilter( diff --git a/lib/src/room.dart b/lib/src/room.dart index 45762257..6a6bfac0 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -852,6 +852,26 @@ class Room { return eventId; } + /// Calculates how secure the communication is. When all devices are blocked or + /// verified, then this returns [EncryptionHealthState.allVerified]. When at + /// least one device is not verified, then it returns + /// [EncryptionHealthState.unverifiedDevices]. Apps should display this health + /// state next to the input text field to inform the user about the current + /// encryption security level. + Future calcEncryptionHealthState() async { + final users = await requestParticipants(); + users.removeWhere((u) => + !{Membership.invite, Membership.join}.contains(u.membership) || + !client.userDeviceKeys.containsKey(u.id)); + + if (users.any((u) => + client.userDeviceKeys[u.id]!.verified != UserVerifiedStatus.verified)) { + return EncryptionHealthState.unverifiedDevices; + } + + return EncryptionHealthState.allVerified; + } + Future _sendContent( String type, Map content, { @@ -1977,3 +1997,8 @@ class Room { @override bool operator ==(dynamic other) => (other is Room && other.id == id); } + +enum EncryptionHealthState { + allVerified, + unverifiedDevices, +} diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index 9661e2cf..baaa298e 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -151,13 +151,13 @@ abstract class SignableKey extends MatrixSignableKey { bool get blocked => _blocked ?? false; set blocked(bool b) => _blocked = b; - bool get encryptToDevice => - !(blocked) && - identifier != null && - ed25519Key != null && - (client.userDeviceKeys[userId]?.masterKey?.verified ?? false - ? verified - : true); + bool get encryptToDevice { + if (blocked) return false; + + if (identifier == null || ed25519Key == null) return false; + + return client.shareKeysWithUnverifiedDevices || verified; + } void setDirectVerified(bool v) { _verified = v; diff --git a/test/device_keys_list_test.dart b/test/device_keys_list_test.dart index 80ac1638..a1bdc991 100644 --- a/test/device_keys_list_test.dart +++ b/test/device_keys_list_test.dart @@ -156,6 +156,11 @@ void main() { }, }, }, client); + expect(client.shareKeysWithUnverifiedDevices, true); + expect(key.encryptToDevice, true); + client.shareKeysWithUnverifiedDevices = false; + expect(key.encryptToDevice, false); + client.shareKeysWithUnverifiedDevices = true; final masterKey = client.userDeviceKeys[client.userID]!.masterKey!; masterKey.setDirectVerified(true); // we need to populate the ssss cache to be able to test signing easily @@ -175,7 +180,7 @@ void main() { expect( client.userDeviceKeys[client.userID]?.deviceKeys['UNSIGNEDDEVICE'] ?.encryptToDevice, - false); + true); expect(masterKey.verified, true); await masterKey.setBlocked(true); diff --git a/test/room_test.dart b/test/room_test.dart index 63d1f47e..b723a0e9 100644 --- a/test/room_test.dart +++ b/test/room_test.dart @@ -349,6 +349,13 @@ void main() { expect(user.room.id, '!localpart:server.abc'); }); + test('calcEncryptionHealthState', () async { + expect( + await room.calcEncryptionHealthState(), + EncryptionHealthState.unverifiedDevices, + ); + }); + test('getEventByID', () async { final event = await room.getEventById('1234'); expect(event?.eventId, '143273582443PhrSn:example.org');