feat: Calc encryption health state and allow key sharing with unknown devices

In order to allow key sharing with
unknown devices (master key has been
verified but this device is not
signed by this master key) the
user should at least be informed.
This makes it possible to set
in the client constructor whether
the app should share keys with
unverified devices or unknown
devices.
This commit is contained in:
Christian Pauly 2022-07-08 13:35:45 +02:00
parent 01e16506dc
commit 28231936b1
5 changed files with 48 additions and 8 deletions

View File

@ -83,6 +83,8 @@ class Client extends MatrixApi {
final bool mxidLocalPartFallback; final bool mxidLocalPartFallback;
bool shareKeysWithUnverifiedDevices;
// For CommandsClientExtension // For CommandsClientExtension
final Map<String, FutureOr<String?> Function(CommandArgs)> commands = {}; final Map<String, FutureOr<String?> Function(CommandArgs)> commands = {};
final Filter syncFilter; final Filter syncFilter;
@ -167,6 +169,7 @@ class Client extends MatrixApi {
Filter? syncFilter, Filter? syncFilter,
this.sendTimelineEventTimeout = const Duration(minutes: 1), this.sendTimelineEventTimeout = const Duration(minutes: 1),
this.customImageResizer, this.customImageResizer,
this.shareKeysWithUnverifiedDevices = true,
}) : syncFilter = syncFilter ?? }) : syncFilter = syncFilter ??
Filter( Filter(
room: RoomFilter( room: RoomFilter(

View File

@ -852,6 +852,26 @@ class Room {
return eventId; 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<EncryptionHealthState> 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<String?> _sendContent( Future<String?> _sendContent(
String type, String type,
Map<String, dynamic> content, { Map<String, dynamic> content, {
@ -1977,3 +1997,8 @@ class Room {
@override @override
bool operator ==(dynamic other) => (other is Room && other.id == id); bool operator ==(dynamic other) => (other is Room && other.id == id);
} }
enum EncryptionHealthState {
allVerified,
unverifiedDevices,
}

View File

@ -151,13 +151,13 @@ abstract class SignableKey extends MatrixSignableKey {
bool get blocked => _blocked ?? false; bool get blocked => _blocked ?? false;
set blocked(bool b) => _blocked = b; set blocked(bool b) => _blocked = b;
bool get encryptToDevice => bool get encryptToDevice {
!(blocked) && if (blocked) return false;
identifier != null &&
ed25519Key != null && if (identifier == null || ed25519Key == null) return false;
(client.userDeviceKeys[userId]?.masterKey?.verified ?? false
? verified return client.shareKeysWithUnverifiedDevices || verified;
: true); }
void setDirectVerified(bool v) { void setDirectVerified(bool v) {
_verified = v; _verified = v;

View File

@ -156,6 +156,11 @@ void main() {
}, },
}, },
}, client); }, 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!; final masterKey = client.userDeviceKeys[client.userID]!.masterKey!;
masterKey.setDirectVerified(true); masterKey.setDirectVerified(true);
// we need to populate the ssss cache to be able to test signing easily // we need to populate the ssss cache to be able to test signing easily
@ -175,7 +180,7 @@ void main() {
expect( expect(
client.userDeviceKeys[client.userID]?.deviceKeys['UNSIGNEDDEVICE'] client.userDeviceKeys[client.userID]?.deviceKeys['UNSIGNEDDEVICE']
?.encryptToDevice, ?.encryptToDevice,
false); true);
expect(masterKey.verified, true); expect(masterKey.verified, true);
await masterKey.setBlocked(true); await masterKey.setBlocked(true);

View File

@ -349,6 +349,13 @@ void main() {
expect(user.room.id, '!localpart:server.abc'); expect(user.room.id, '!localpart:server.abc');
}); });
test('calcEncryptionHealthState', () async {
expect(
await room.calcEncryptionHealthState(),
EncryptionHealthState.unverifiedDevices,
);
});
test('getEventByID', () async { test('getEventByID', () async {
final event = await room.getEventById('1234'); final event = await room.getEventById('1234');
expect(event?.eventId, '143273582443PhrSn:example.org'); expect(event?.eventId, '143273582443PhrSn:example.org');