Add e2ee settings

This commit is contained in:
Christian Pauly 2020-02-04 14:42:35 +01:00
parent 178e50a564
commit c6c419f76d
15 changed files with 357 additions and 50 deletions

View file

@ -0,0 +1,36 @@
import 'package:fluffychat/i18n/i18n.dart';
import 'package:flutter/material.dart';
class ConfirmDialog extends StatelessWidget {
const ConfirmDialog(
this.text,
this.confirmText,
this.onConfirmed,
);
final String text;
final String confirmText;
final Function(BuildContext) onConfirmed;
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(text),
actions: <Widget>[
FlatButton(
child: Text(I18n.of(context).close.toUpperCase(),
style: TextStyle(color: Colors.blueGrey)),
onPressed: () => Navigator.of(context).pop(),
),
FlatButton(
child: Text(
confirmText.toUpperCase(),
),
onPressed: () {
Navigator.of(context).pop();
onConfirmed(context);
},
),
],
);
}
}

View file

@ -1,36 +0,0 @@
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/i18n/i18n.dart';
import 'package:flutter/material.dart';
import '../matrix.dart';
class RedactMessageDialog extends StatelessWidget {
final Event event;
const RedactMessageDialog(this.event);
void removeAction(BuildContext context) {
Matrix.of(context).tryRequestWithLoadingDialog(event.redact());
Navigator.of(context).pop();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(I18n.of(context).messageWillBeRemovedWarning),
actions: <Widget>[
FlatButton(
child: Text("Close".toUpperCase(),
style: TextStyle(color: Colors.blueGrey)),
onPressed: () => Navigator.of(context).pop(),
),
FlatButton(
child: Text(
I18n.of(context).remove.toUpperCase(),
style: TextStyle(color: Colors.red),
),
onPressed: () => removeAction(context),
),
],
);
}
}

View file

@ -1,6 +1,6 @@
import 'package:bubble/bubble.dart';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/components/dialogs/redact_message_dialog.dart';
import 'package:fluffychat/components/dialogs/confirm_dialog.dart';
import 'package:fluffychat/components/message_content.dart';
import 'package:fluffychat/i18n/i18n.dart';
import 'package:fluffychat/utils/app_route.dart';
@ -107,7 +107,12 @@ class Message extends StatelessWidget {
case "remove":
await showDialog(
context: context,
builder: (BuildContext context) => RedactMessageDialog(event),
builder: (BuildContext context) => ConfirmDialog(
I18n.of(context).messageWillBeRemovedWarning,
I18n.of(context).remove, (context) {
Matrix.of(context)
.tryRequestWithLoadingDialog(event.redact());
}),
);
break;
case "resend":

View file

@ -265,6 +265,14 @@ class I18n {
String get emptyChat => Intl.message("Empty chat");
String get enableEncryptionWarning => Intl.message(
"You won't be able to disable the encryption anymore. Are you sure?");
String get encryptionAlgorithm => Intl.message("Encryption algorithm");
String get end2endEncryptionSettings =>
Intl.message("End-to-end encryption settings");
String get enterAGroupName => Intl.message("Enter a group name");
String get enterAUsername => Intl.message("Enter a username");
@ -388,6 +396,9 @@ class I18n {
String get muteChat => Intl.message('Mute chat');
String get needPantalaimonWarning => Intl.message(
"Please be aware that you need Pantalaimon to use end-to-end encryption for now.");
String get newMessageInFluffyChat =>
Intl.message('New message in FluffyChat');
@ -396,6 +407,8 @@ class I18n {
String get noGoogleServicesWarning => Intl.message(
"It seems that you have no google services on your phone. That's a good decision for your privacy! To receive push notifications in FluffyChat we recommend using microG: https://microg.org/");
String get none => Intl.message("None");
String get noRoomsFound => Intl.message("No rooms found...");
String get notSupportedInWeb => Intl.message("Not supported in web");
@ -407,6 +420,9 @@ class I18n {
String get optionalGroupName => Intl.message("(Optional) Group name");
String get participatingUserDevices =>
Intl.message("Participating user devices");
String get password => Intl.message("Password");
String play(String fileName) => Intl.message(
@ -637,6 +653,8 @@ class I18n {
String get writeAMessage => Intl.message("Write a message...");
String get yes => Intl.message("Yes");
String get you => Intl.message("You");
String get youAreInvitedToThisChat =>

View file

@ -352,6 +352,21 @@
"type": "text",
"placeholders": {}
},
"You won't be able to disable the encryption anymore. Are you sure?": "Du wirst die Verschlüsselung nicht mehr ausstellen können. Bist Du sicher?",
"@You won't be able to disable the encryption anymore. Are you sure?": {
"type": "text",
"placeholders": {}
},
"Encryption algorithm": "Verschlüsselungsalgorithmus",
"@Encryption algorithm": {
"type": "text",
"placeholders": {}
},
"End-to-end encryption settings": "Ende-zu-Ende-Verschlüsselung",
"@End-to-end encryption settings": {
"type": "text",
"placeholders": {}
},
"Enter a group name": "Gib einen Gruppennamen ein",
"@Enter a group name": {
"type": "text",
@ -579,6 +594,11 @@
"type": "text",
"placeholders": {}
},
"Please be aware that you need Pantalaimon to use end-to-end encryption for now.": "Bitte beachte, dass du Pantalaimon brauchst, um Ende-zu-Ende-Verschlüsselung benutzen zu können.",
"@Please be aware that you need Pantalaimon to use end-to-end encryption for now.": {
"type": "text",
"placeholders": {}
},
"New message in FluffyChat": "Neue Nachricht in FluffyChat",
"@New message in FluffyChat": {
"type": "text",
@ -594,6 +614,11 @@
"type": "text",
"placeholders": {}
},
"None": "Keiner",
"@None": {
"type": "text",
"placeholders": {}
},
"No rooms found...": "Keine Räume gefunden ...",
"@No rooms found...": {
"type": "text",
@ -619,6 +644,11 @@
"type": "text",
"placeholders": {}
},
"Participating user devices": "Teilnehmende Geräte",
"@Participating user devices": {
"type": "text",
"placeholders": {}
},
"Password": "Passwort",
"@Password": {
"type": "text",
@ -984,6 +1014,11 @@
"type": "text",
"placeholders": {}
},
"Yes": "Ja",
"@Yes": {
"type": "text",
"placeholders": {}
},
"You": "Du",
"@You": {
"type": "text",

View file

@ -1,5 +1,5 @@
{
"@@last_modified": "2020-01-27T11:02:06.464591",
"@@last_modified": "2020-02-04T14:02:33.828211",
"About": "About",
"@About": {
"type": "text",
@ -352,6 +352,21 @@
"type": "text",
"placeholders": {}
},
"You won't be able to disable the encryption anymore. Are you sure?": "You won't be able to disable the encryption anymore. Are you sure?",
"@You won't be able to disable the encryption anymore. Are you sure?": {
"type": "text",
"placeholders": {}
},
"Encryption algorithm": "Encryption algorithm",
"@Encryption algorithm": {
"type": "text",
"placeholders": {}
},
"End-to-end encryption settings": "End-to-end encryption settings",
"@End-to-end encryption settings": {
"type": "text",
"placeholders": {}
},
"Enter a group name": "Enter a group name",
"@Enter a group name": {
"type": "text",
@ -579,6 +594,11 @@
"type": "text",
"placeholders": {}
},
"Please be aware that you need Pantalaimon to use end-to-end encryption for now.": "Please be aware that you need Pantalaimon to use end-to-end encryption for now.",
"@Please be aware that you need Pantalaimon to use end-to-end encryption for now.": {
"type": "text",
"placeholders": {}
},
"New message in FluffyChat": "New message in FluffyChat",
"@New message in FluffyChat": {
"type": "text",
@ -594,6 +614,11 @@
"type": "text",
"placeholders": {}
},
"None": "None",
"@None": {
"type": "text",
"placeholders": {}
},
"No rooms found...": "No rooms found...",
"@No rooms found...": {
"type": "text",
@ -619,6 +644,11 @@
"type": "text",
"placeholders": {}
},
"Participating user devices": "Participating user devices",
"@Participating user devices": {
"type": "text",
"placeholders": {}
},
"Password": "Password",
"@Password": {
"type": "text",
@ -984,6 +1014,11 @@
"type": "text",
"placeholders": {}
},
"Yes": "Yes",
"@Yes": {
"type": "text",
"placeholders": {}
},
"You": "You",
"@You": {
"type": "text",

View file

@ -166,6 +166,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Donate" : MessageLookupByLibrary.simpleMessage("Spenden"),
"Edit displayname" : MessageLookupByLibrary.simpleMessage("Anzeigename ändern"),
"Empty chat" : MessageLookupByLibrary.simpleMessage("Leerer Chat"),
"Encryption algorithm" : MessageLookupByLibrary.simpleMessage("Verschlüsselungsalgorithmus"),
"End-to-end encryption settings" : MessageLookupByLibrary.simpleMessage("Ende-zu-Ende-Verschlüsselung"),
"Enter a group name" : MessageLookupByLibrary.simpleMessage("Gib einen Gruppennamen ein"),
"Enter a username" : MessageLookupByLibrary.simpleMessage("Gib einen Benutzernamen ein"),
"FluffyChat" : MessageLookupByLibrary.simpleMessage("FluffyChat"),
@ -201,10 +203,13 @@ class MessageLookup extends MessageLookupByLibrary {
"New message in FluffyChat" : MessageLookupByLibrary.simpleMessage("Neue Nachricht in FluffyChat"),
"New private chat" : MessageLookupByLibrary.simpleMessage("Neuer privater Chat"),
"No rooms found..." : MessageLookupByLibrary.simpleMessage("Keine Räume gefunden ..."),
"None" : MessageLookupByLibrary.simpleMessage("Keiner"),
"Not supported in web" : MessageLookupByLibrary.simpleMessage("Wird in der Web-Version nicht unterstützt"),
"Oops something went wrong..." : MessageLookupByLibrary.simpleMessage("Hoppla! Da ist etwas schief gelaufen ..."),
"Open camera" : MessageLookupByLibrary.simpleMessage("Kamera öffnen"),
"Participating user devices" : MessageLookupByLibrary.simpleMessage("Teilnehmende Geräte"),
"Password" : MessageLookupByLibrary.simpleMessage("Passwort"),
"Please be aware that you need Pantalaimon to use end-to-end encryption for now." : MessageLookupByLibrary.simpleMessage("Bitte beachte, dass du Pantalaimon brauchst, um Ende-zu-Ende-Verschlüsselung benutzen zu können."),
"Please choose a username" : MessageLookupByLibrary.simpleMessage("Bitte wähle einen Benutzernamen"),
"Please enter a matrix identifier" : MessageLookupByLibrary.simpleMessage("Bitte eine Matrix ID eingeben"),
"Please enter your password" : MessageLookupByLibrary.simpleMessage("Bitte dein Passwort eingeben"),
@ -242,11 +247,13 @@ class MessageLookup extends MessageLookupByLibrary {
"Wednesday" : MessageLookupByLibrary.simpleMessage("Mittwoch"),
"Who is allowed to join this group" : MessageLookupByLibrary.simpleMessage("Wer darf der Gruppe beitreten"),
"Write a message..." : MessageLookupByLibrary.simpleMessage("Schreibe eine Nachricht ..."),
"Yes" : MessageLookupByLibrary.simpleMessage("Ja"),
"You" : MessageLookupByLibrary.simpleMessage("Du"),
"You are invited to this chat" : MessageLookupByLibrary.simpleMessage("Du wurdest eingeladen in diesen Chat"),
"You are no longer participating in this chat" : MessageLookupByLibrary.simpleMessage("Du bist kein Teilnehmer mehr in diesem Chat"),
"You cannot invite yourself" : MessageLookupByLibrary.simpleMessage("Du kannst dich nicht selbst einladen"),
"You have been banned from this chat" : MessageLookupByLibrary.simpleMessage("Du wurdest aus dem Chat verbannt"),
"You won\'t be able to disable the encryption anymore. Are you sure?" : MessageLookupByLibrary.simpleMessage("Du wirst die Verschlüsselung nicht mehr ausstellen können. Bist Du sicher?"),
"Your own username" : MessageLookupByLibrary.simpleMessage("Dein eigener Benutzername"),
"acceptedTheInvitation" : m0,
"activatedEndToEndEncryption" : m1,

View file

@ -166,6 +166,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Donate" : MessageLookupByLibrary.simpleMessage("Donate"),
"Edit displayname" : MessageLookupByLibrary.simpleMessage("Edit displayname"),
"Empty chat" : MessageLookupByLibrary.simpleMessage("Empty chat"),
"Encryption algorithm" : MessageLookupByLibrary.simpleMessage("Encryption algorithm"),
"End-to-end encryption settings" : MessageLookupByLibrary.simpleMessage("End-to-end encryption settings"),
"Enter a group name" : MessageLookupByLibrary.simpleMessage("Enter a group name"),
"Enter a username" : MessageLookupByLibrary.simpleMessage("Enter a username"),
"FluffyChat" : MessageLookupByLibrary.simpleMessage("FluffyChat"),
@ -201,10 +203,13 @@ class MessageLookup extends MessageLookupByLibrary {
"New message in FluffyChat" : MessageLookupByLibrary.simpleMessage("New message in FluffyChat"),
"New private chat" : MessageLookupByLibrary.simpleMessage("New private chat"),
"No rooms found..." : MessageLookupByLibrary.simpleMessage("No rooms found..."),
"None" : MessageLookupByLibrary.simpleMessage("None"),
"Not supported in web" : MessageLookupByLibrary.simpleMessage("Not supported in web"),
"Oops something went wrong..." : MessageLookupByLibrary.simpleMessage("Oops something went wrong..."),
"Open camera" : MessageLookupByLibrary.simpleMessage("Open camera"),
"Participating user devices" : MessageLookupByLibrary.simpleMessage("Participating user devices"),
"Password" : MessageLookupByLibrary.simpleMessage("Password"),
"Please be aware that you need Pantalaimon to use end-to-end encryption for now." : MessageLookupByLibrary.simpleMessage("Please be aware that you need Pantalaimon to use end-to-end encryption for now."),
"Please choose a username" : MessageLookupByLibrary.simpleMessage("Please choose a username"),
"Please enter a matrix identifier" : MessageLookupByLibrary.simpleMessage("Please enter a matrix identifier"),
"Please enter your password" : MessageLookupByLibrary.simpleMessage("Please enter your password"),
@ -242,11 +247,13 @@ class MessageLookup extends MessageLookupByLibrary {
"Wednesday" : MessageLookupByLibrary.simpleMessage("Wednesday"),
"Who is allowed to join this group" : MessageLookupByLibrary.simpleMessage("Who is allowed to join this group"),
"Write a message..." : MessageLookupByLibrary.simpleMessage("Write a message..."),
"Yes" : MessageLookupByLibrary.simpleMessage("Yes"),
"You" : MessageLookupByLibrary.simpleMessage("You"),
"You are invited to this chat" : MessageLookupByLibrary.simpleMessage("You are invited to this chat"),
"You are no longer participating in this chat" : MessageLookupByLibrary.simpleMessage("You are no longer participating in this chat"),
"You cannot invite yourself" : MessageLookupByLibrary.simpleMessage("You cannot invite yourself"),
"You have been banned from this chat" : MessageLookupByLibrary.simpleMessage("You have been banned from this chat"),
"You won\'t be able to disable the encryption anymore. Are you sure?" : MessageLookupByLibrary.simpleMessage("You won\'t be able to disable the encryption anymore. Are you sure?"),
"Your own username" : MessageLookupByLibrary.simpleMessage("Your own username"),
"acceptedTheInvitation" : m0,
"activatedEndToEndEncryption" : m1,

View file

@ -0,0 +1,15 @@
extension BeautifyStringExtension on String {
String get beautified {
String beautifiedStr = "";
for (int i = 0; i < this.length; i++) {
beautifiedStr += this.substring(i, i + 1);
if (i % 4 == 3) {
beautifiedStr += " ";
}
if (i % 16 == 15) {
beautifiedStr += "\n";
}
}
return beautifiedStr;
}
}

View file

@ -162,7 +162,9 @@ extension LocalizedBody on Event {
break;
case EventTypes.Encryption:
localizedBody =
I18n.of(context).activatedEndToEndEncryption(senderName);
I18n.of(context).activatedEndToEndEncryption(senderName) +
". " +
I18n.of(context).needPantalaimonWarning;
break;
case EventTypes.Encrypted:
localizedBody = I18n.of(context).couldNotDecryptMessage;

View file

@ -36,6 +36,24 @@ class Store extends StoreAPI {
return await secureStorage.write(key: key, value: value);
}
Future<Map<String, DeviceKeysList>> getUserDeviceKeys() async {
final deviceKeysListString = await getItem(_UserDeviceKeysKey);
if (deviceKeysListString == null) return {};
Map<String, dynamic> rawUserDeviceKeys = json.decode(deviceKeysListString);
Map<String, DeviceKeysList> userDeviceKeys = {};
for (final entry in rawUserDeviceKeys.entries) {
userDeviceKeys[entry.key] = DeviceKeysList.fromJson(entry.value);
}
return userDeviceKeys;
}
Future<void> storeUserDeviceKeys(
Map<String, DeviceKeysList> userDeviceKeys) async {
await setItem(_UserDeviceKeysKey, json.encode(userDeviceKeys));
}
String get _UserDeviceKeysKey => "${client.clientName}.user_device_keys";
_init() async {
final credentialsStr = await getItem(client.clientName);

View file

@ -8,7 +8,9 @@ import 'package:fluffychat/components/chat_settings_popup_menu.dart';
import 'package:fluffychat/components/list_items/message.dart';
import 'package:fluffychat/components/matrix.dart';
import 'package:fluffychat/i18n/i18n.dart';
import 'package:fluffychat/utils/app_route.dart';
import 'package:fluffychat/utils/room_extension.dart';
import 'package:fluffychat/views/chat_encryption_settings.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
@ -422,6 +424,17 @@ class _ChatState extends State<_Chat> {
),
)),
SizedBox(width: 8),
if (sendController.text.isEmpty)
IconButton(
icon: Icon(
room.encrypted ? Icons.lock : Icons.lock_open),
onPressed: () => Navigator.of(context).push(
AppRoute.defaultRoute(
context,
ChatEncryptionSettingsView(widget.id),
),
),
),
IconButton(
icon: Icon(Icons.send),
onPressed: () => send(),

View file

@ -0,0 +1,159 @@
import 'dart:async';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/components/adaptive_page_layout.dart';
import 'package:fluffychat/components/dialogs/confirm_dialog.dart';
import 'package:fluffychat/components/matrix.dart';
import 'package:fluffychat/utils/beautify_string_extension.dart';
import 'package:fluffychat/i18n/i18n.dart';
import 'package:fluffychat/views/chat_list.dart';
import 'package:flutter/material.dart';
class ChatEncryptionSettingsView extends StatelessWidget {
final String id;
const ChatEncryptionSettingsView(this.id, {Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return AdaptivePageLayout(
firstScaffold: ChatList(
activeChat: id,
),
secondScaffold: ChatEncryptionSettings(id),
primaryPage: FocusPage.SECOND,
);
}
}
class ChatEncryptionSettings extends StatefulWidget {
final String id;
const ChatEncryptionSettings(this.id, {Key key}) : super(key: key);
@override
_ChatEncryptionSettingsState createState() => _ChatEncryptionSettingsState();
}
class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> {
Room room;
StreamSubscription roomUpdate;
@override
void dispose() {
roomUpdate?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
room ??= Matrix.of(context).client.getRoomById(widget.id);
roomUpdate ??= room.onUpdate.stream.listen((s) => setState(() => null));
return Scaffold(
appBar: AppBar(
title: Text(I18n.of(context).end2endEncryptionSettings),
),
body: Column(
children: <Widget>[
ListTile(
title: Text(I18n.of(context).encryptionAlgorithm),
subtitle: Text(room.encryptionAlgorithm ?? I18n.of(context).none),
trailing: Icon(room.encrypted ? Icons.lock : Icons.lock_open),
onTap: () {
if (room.encrypted) return;
showDialog(
context: context,
builder: (BuildContext context) => ConfirmDialog(
I18n.of(context).enableEncryptionWarning +
" " +
I18n.of(context).needPantalaimonWarning,
I18n.of(context).yes,
(context) => Matrix.of(context).tryRequestWithLoadingDialog(
room.enableEncryption(),
),
),
);
},
),
ListTile(
trailing: Icon(Icons.info),
subtitle: Text(
I18n.of(context).needPantalaimonWarning,
),
),
Divider(height: 1),
ListTile(
title: Text(
"${I18n.of(context).participatingUserDevices}:",
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
),
Divider(height: 1),
FutureBuilder<List<DeviceKeys>>(
future: room.getUserDeviceKeys(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(I18n.of(context).oopsSomethingWentWrong +
": " +
snapshot.error.toString()),
);
}
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
final List<DeviceKeys> deviceKeys = snapshot.data;
return Expanded(
child: ListView.separated(
separatorBuilder: (BuildContext context, int i) =>
Divider(height: 1),
itemCount: deviceKeys.length,
itemBuilder: (BuildContext context, int i) =>
CheckboxListTile(
title: Text(
"${deviceKeys[i].userId} - ${deviceKeys[i].deviceId}",
style: TextStyle(
color: deviceKeys[i].blocked
? Colors.red
: deviceKeys[i].verified
? Colors.green
: Colors.orange),
),
subtitle: Text(
deviceKeys[i]
.keys["ed25519:${deviceKeys[i].deviceId}"]
.beautified,
style: TextStyle(color: Colors.black),
),
value: deviceKeys[i].verified,
onChanged: (bool newVal) {
if (newVal == true) {
if (deviceKeys[i].blocked) {
deviceKeys[i]
.setBlocked(false, Matrix.of(context).client);
}
deviceKeys[i]
.setVerified(true, Matrix.of(context).client);
} else {
if (deviceKeys[i].verified) {
deviceKeys[i]
.setVerified(false, Matrix.of(context).client);
}
deviceKeys[i]
.setBlocked(true, Matrix.of(context).client);
}
setState(() => null);
},
),
),
);
},
),
],
),
);
}
}

View file

@ -110,8 +110,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "103eb6734b3c37ffbd124142473bd0080e814f59"
resolved-ref: "103eb6734b3c37ffbd124142473bd0080e814f59"
ref: "166b1a9464843cd17e86d9330ac8782d6081f46b"
resolved-ref: "166b1a9464843cd17e86d9330ac8782d6081f46b"
url: "https://gitlab.com/famedly/famedlysdk.git"
source: git
version: "0.0.1"
@ -247,13 +247,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.1+1"
json_annotation:
dependency: transitive
description:
name: json_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.0"
link_text:
dependency: "direct main"
description:

View file

@ -27,7 +27,7 @@ dependencies:
famedlysdk:
git:
url: https://gitlab.com/famedly/famedlysdk.git
ref: 103eb6734b3c37ffbd124142473bd0080e814f59
ref: 166b1a9464843cd17e86d9330ac8782d6081f46b
localstorage: ^3.0.1+4
bubble: ^1.1.9+1