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:
parent
01e16506dc
commit
28231936b1
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue