feat: Better encryption / verification

This commit is contained in:
Sorunome 2020-10-31 10:39:38 +01:00
parent ede963fffc
commit 1ff986e7be
No known key found for this signature in database
GPG Key ID: B19471D07FC9BE9C
8 changed files with 140 additions and 48 deletions

View File

@ -1,3 +1,11 @@
# Version 0.22.0
### Features
- Broadcast self-verification
### Changes
- Undecryptable events have a "verify" button, if you haven't verified yet
- User bottom sheet lists verified status
- Lock icon next to input bar can now be red
# Version 0.21.0 - 2020-10-28 # Version 0.21.0 - 2020-10-28
### Features ### Features
- New user viewer - New user viewer

View File

@ -62,20 +62,30 @@ class _EncryptionButtonState extends State<EncryptionButton> {
.onSync .onSync
.stream .stream
.listen((s) => setState(() => null)); .listen((s) => setState(() => null));
return FutureBuilder<List<DeviceKeys>>( return FutureBuilder<List<User>>(
future: widget.room.encrypted ? widget.room.getUserDeviceKeys() : null, future:
widget.room.encrypted ? widget.room.requestParticipants() : null,
builder: (BuildContext context, snapshot) { builder: (BuildContext context, snapshot) {
Color color; Color color;
if (widget.room.encrypted && snapshot.hasData) { if (widget.room.encrypted && snapshot.hasData) {
var data = snapshot.data; final users = snapshot.data;
final deviceKeysList = data; users.removeWhere((u) =>
color = Colors.orange; !{Membership.invite, Membership.join}.contains(u.membership) ||
if (deviceKeysList.indexWhere((DeviceKeys deviceKeys) => !widget.room.client.userDeviceKeys.containsKey(u.id));
deviceKeys.verified == false && var allUsersValid = true;
deviceKeys.blocked == false) == var oneUserInvalid = false;
-1) { for (final u in users) {
color = Colors.black.withGreen(220).withOpacity(0.75); final status = widget.room.client.userDeviceKeys[u.id].verified;
if (status != UserVerifiedStatus.verified) {
allUsersValid = false;
}
if (status == UserVerifiedStatus.unknownDevice) {
oneUserInvalid = true;
}
} }
color = oneUserInvalid
? Colors.red
: (allUsersValid ? Colors.green : Colors.orange);
} else if (!widget.room.encrypted && } else if (!widget.room.encrypted &&
widget.room.joinRules != JoinRules.public) { widget.room.joinRules != JoinRules.public) {
color = null; color = null;

View File

@ -1,4 +1,5 @@
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/encryption.dart';
import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; import 'package:fluffychat/components/dialogs/simple_dialogs.dart';
import 'package:fluffychat/components/message_content.dart'; import 'package:fluffychat/components/message_content.dart';
import 'package:fluffychat/components/reply_content.dart'; import 'package:fluffychat/components/reply_content.dart';
@ -13,6 +14,8 @@ import '../avatar.dart';
import '../matrix.dart'; import '../matrix.dart';
import '../message_reactions.dart'; import '../message_reactions.dart';
import 'state_message.dart'; import 'state_message.dart';
import '../../views/key_verification.dart';
import '../../utils/app_route.dart';
class Message extends StatelessWidget { class Message extends StatelessWidget {
final Event event; final Event event;
@ -37,6 +40,36 @@ class Message extends StatelessWidget {
/// of touchscreen. /// of touchscreen.
static bool useMouse = false; static bool useMouse = false;
void _verifyOrRequestKey(BuildContext context) async {
final client = Matrix.of(context).client;
if (client.isUnknownSession && client.encryption.crossSigning.enabled) {
final req =
await client.userDeviceKeys[client.userID].startVerification();
req.onUpdate = () async {
if (req.state == KeyVerificationState.done) {
for (var i = 0; i < 12; i++) {
if (await client.encryption.keyManager.isCached()) {
break;
}
await Future.delayed(Duration(seconds: 1));
}
final timeline = await event.room.getTimeline();
timeline.requestKeys();
timeline.cancelSubscriptions();
}
};
await Navigator.of(context).push(
AppRoute.defaultRoute(
context,
KeyVerificationView(request: req),
),
);
} else {
await SimpleDialogs(context).tryRequestWithLoadingDialog(
event.getDisplayEvent(timeline).requestKey());
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (event.type == EventTypes.Unknown) { if (event.type == EventTypes.Unknown) {
@ -137,12 +170,13 @@ class Message extends StatelessWidget {
RaisedButton( RaisedButton(
color: color.withAlpha(100), color: color.withAlpha(100),
child: Text( child: Text(
L10n.of(context).requestPermission, client.isUnknownSession &&
client.encryption.crossSigning.enabled
? L10n.of(context).verify
: L10n.of(context).requestPermission,
style: TextStyle(color: textColor), style: TextStyle(color: textColor),
), ),
onPressed: () => SimpleDialogs(context) onPressed: () => _verifyOrRequestKey(context),
.tryRequestWithLoadingDialog(
displayEvent.requestKey()),
), ),
SizedBox(height: 4), SizedBox(height: 4),
Opacity( Opacity(

View File

@ -269,10 +269,21 @@ class MatrixState extends State<Matrix> {
}); });
onKeyVerificationRequestSub ??= client.onKeyVerificationRequest.stream onKeyVerificationRequestSub ??= client.onKeyVerificationRequest.stream
.listen((KeyVerification request) async { .listen((KeyVerification request) async {
var hidPopup = false;
request.onUpdate = () {
if (!hidPopup &&
{KeyVerificationState.done, KeyVerificationState.error}
.contains(request.state)) {
Navigator.of(context, rootNavigator: true).pop('dialog');
}
hidPopup = true;
};
if (await SimpleDialogs(context).askConfirmation( if (await SimpleDialogs(context).askConfirmation(
titleText: L10n.of(context).newVerificationRequest, titleText: L10n.of(context).newVerificationRequest,
contentText: L10n.of(context).askVerificationRequest(request.userId), contentText: L10n.of(context).askVerificationRequest(request.userId),
)) { )) {
request.onUpdate = null;
hidPopup = true;
await request.acceptVerification(); await request.acceptVerification();
await Navigator.of(context).push( await Navigator.of(context).push(
AppRoute.defaultRoute( AppRoute.defaultRoute(
@ -281,6 +292,8 @@ class MatrixState extends State<Matrix> {
), ),
); );
} else { } else {
request.onUpdate = null;
hidPopup = true;
await request.rejectVerification(); await request.rejectVerification();
} }
}); });

View File

@ -12,6 +12,8 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
import '../utils/presence_extension.dart'; import '../utils/presence_extension.dart';
import 'dialogs/simple_dialogs.dart'; import 'dialogs/simple_dialogs.dart';
import 'matrix.dart'; import 'matrix.dart';
import '../views/key_verification.dart';
import '../utils/app_route.dart';
class UserBottomSheet extends StatelessWidget { class UserBottomSheet extends StatelessWidget {
final User user; final User user;
@ -72,9 +74,22 @@ class UserBottomSheet extends StatelessWidget {
} }
} }
void _verifyAction(BuildContext context) async {
final client = Matrix.of(context).client;
final req = await client.userDeviceKeys[user.id].startVerification();
await Navigator.of(context).push(
AppRoute.defaultRoute(
context,
KeyVerificationView(request: req),
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final presence = Matrix.of(context).client.presences[user.id]; final client = Matrix.of(context).client;
final presence = client.presences[user.id];
final verificationStatus = client.userDeviceKeys[user.id]?.verified;
var items = <PopupMenuEntry<String>>[]; var items = <PopupMenuEntry<String>>[];
if (onMention != null) { if (onMention != null) {
@ -145,6 +160,21 @@ class UserBottomSheet extends StatelessWidget {
), ),
title: Text(user.calcDisplayname()), title: Text(user.calcDisplayname()),
actions: [ actions: [
if (verificationStatus != null)
InkWell(
child: Icon(
Icons.lock,
color: {
UserVerifiedStatus.unknownDevice: Colors.red,
UserVerifiedStatus.verified: Colors.green,
}[verificationStatus] ??
Colors.orange,
),
onTap: () =>
verificationStatus == UserVerifiedStatus.unknown
? _verifyAction(context)
: null,
),
if (user.id != Matrix.of(context).client.userID) if (user.id != Matrix.of(context).client.userID)
PopupMenuButton( PopupMenuButton(
itemBuilder: (_) => items, itemBuilder: (_) => items,

View File

@ -143,11 +143,10 @@ class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> {
itemBuilder: (c) { itemBuilder: (c) {
var items = <PopupMenuEntry<String>>[]; var items = <PopupMenuEntry<String>>[];
if (room if (room
.client .client
.userDeviceKeys[deviceKeys[i].userId] .userDeviceKeys[deviceKeys[i].userId]
.verified == .verified ==
UserVerifiedStatus.unknown && UserVerifiedStatus.unknown) {
deviceKeys[i].userId != room.client.userID) {
items.add(PopupMenuItem( items.add(PopupMenuItem(
child: Text(L10n.of(context).verifyUser), child: Text(L10n.of(context).verifyUser),
value: 'verify_user', value: 'verify_user',
@ -211,7 +210,7 @@ class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> {
}, },
child: ListTile( child: ListTile(
title: Text( title: Text(
"${deviceKeys[i].unsigned["device_display_name"] ?? L10n.of(context).unknownDevice} - ${deviceKeys[i].deviceId}", '${deviceKeys[i].deviceDisplayName ?? L10n.of(context).unknownDevice} - ${deviceKeys[i].deviceId}',
style: TextStyle( style: TextStyle(
color: deviceKeys[i].blocked color: deviceKeys[i].blocked
? Colors.red ? Colors.red
@ -220,9 +219,7 @@ class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> {
: Colors.orange), : Colors.orange),
), ),
subtitle: Text( subtitle: Text(
deviceKeys[i] deviceKeys[i].ed25519Key.beautified,
.keys['ed25519:${deviceKeys[i].deviceId}']
.beautified,
style: TextStyle( style: TextStyle(
color: Theme.of(context) color: Theme.of(context)
.textTheme .textTheme

View File

@ -49,7 +49,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.5.0-nullsafety.2" version: "2.5.0-nullsafety.1"
base58check: base58check:
dependency: transitive dependency: transitive
description: description:
@ -63,7 +63,7 @@ packages:
name: boolean_selector name: boolean_selector
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0-nullsafety.2" version: "2.1.0-nullsafety.1"
bot_toast: bot_toast:
dependency: "direct main" dependency: "direct main"
description: description:
@ -98,14 +98,14 @@ packages:
name: characters name: characters
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0-nullsafety.4" version: "1.1.0-nullsafety.3"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
name: charcode name: charcode
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0-nullsafety.2" version: "1.2.0-nullsafety.1"
circular_check_box: circular_check_box:
dependency: "direct main" dependency: "direct main"
description: description:
@ -126,14 +126,14 @@ packages:
name: clock name: clock
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0-nullsafety.2" version: "1.1.0-nullsafety.1"
collection: collection:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.15.0-nullsafety.4" version: "1.15.0-nullsafety.3"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -203,13 +203,13 @@ packages:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0-nullsafety.2" version: "1.2.0-nullsafety.1"
famedlysdk: famedlysdk:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "955fb747c29eab76b17eb9a13ebc15026e917fb8" ref: "15d817023d34f813e95eba6ca8c71c575b8c2457"
resolved-ref: "955fb747c29eab76b17eb9a13ebc15026e917fb8" resolved-ref: "15d817023d34f813e95eba6ca8c71c575b8c2457"
url: "https://gitlab.com/famedly/famedlysdk.git" url: "https://gitlab.com/famedly/famedlysdk.git"
source: git source: git
version: "0.0.1" version: "0.0.1"
@ -540,7 +540,7 @@ packages:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.10-nullsafety.2" version: "0.12.10-nullsafety.1"
matrix_file_e2ee: matrix_file_e2ee:
dependency: transitive dependency: transitive
description: description:
@ -561,7 +561,7 @@ packages:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0-nullsafety.5" version: "1.3.0-nullsafety.3"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@ -661,7 +661,7 @@ packages:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0-nullsafety.2" version: "1.8.0-nullsafety.1"
path_provider: path_provider:
dependency: "direct main" dependency: "direct main"
description: description:
@ -876,7 +876,7 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0-nullsafety.3" version: "1.8.0-nullsafety.2"
sqflite: sqflite:
dependency: "direct main" dependency: "direct main"
description: description:
@ -911,21 +911,21 @@ packages:
name: stack_trace name: stack_trace
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.10.0-nullsafety.5" version: "1.10.0-nullsafety.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0-nullsafety.2" version: "2.1.0-nullsafety.1"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
name: string_scanner name: string_scanner
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0-nullsafety.2" version: "1.1.0-nullsafety.1"
swipe_to_action: swipe_to_action:
dependency: "direct main" dependency: "direct main"
description: description:
@ -946,28 +946,28 @@ packages:
name: term_glyph name: term_glyph
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0-nullsafety.2" version: "1.2.0-nullsafety.1"
test: test:
dependency: transitive dependency: transitive
description: description:
name: test name: test
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.16.0-nullsafety.7" version: "1.16.0-nullsafety.5"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.19-nullsafety.4" version: "0.2.19-nullsafety.2"
test_core: test_core:
dependency: transitive dependency: transitive
description: description:
name: test_core name: test_core
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.3.12-nullsafety.7" version: "0.3.12-nullsafety.5"
timezone: timezone:
dependency: transitive dependency: transitive
description: description:
@ -981,7 +981,7 @@ packages:
name: typed_data name: typed_data
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0-nullsafety.4" version: "1.3.0-nullsafety.3"
universal_html: universal_html:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1065,7 +1065,7 @@ packages:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0-nullsafety.4" version: "2.1.0-nullsafety.3"
vm_service: vm_service:
dependency: transitive dependency: transitive
description: description:
@ -1137,5 +1137,5 @@ packages:
source: hosted source: hosted
version: "0.1.2" version: "0.1.2"
sdks: sdks:
dart: ">=2.11.0-0.0 <=2.11.0-260.0.dev" dart: ">=2.10.2 <=2.11.0-161.0.dev"
flutter: ">=1.22.2 <2.0.0" flutter: ">=1.22.2 <2.0.0"

View File

@ -23,7 +23,7 @@ dependencies:
famedlysdk: famedlysdk:
git: git:
url: https://gitlab.com/famedly/famedlysdk.git url: https://gitlab.com/famedly/famedlysdk.git
ref: 955fb747c29eab76b17eb9a13ebc15026e917fb8 ref: 15d817023d34f813e95eba6ca8c71c575b8c2457
localstorage: ^3.0.3+6 localstorage: ^3.0.3+6
file_picker_cross: 4.2.2 file_picker_cross: 4.2.2