E2EE and Keypair

This commit is contained in:
2024-05-12 20:15:12 +08:00
parent 08d0a99b10
commit 98547708af
20 changed files with 665 additions and 115 deletions

View File

@ -1,18 +1,97 @@
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:provider/provider.dart';
import 'package:solian/models/message.dart';
import 'package:solian/providers/keypair.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class ChatMessageContent extends StatelessWidget {
class ChatMessageContent extends StatefulWidget {
final Message item;
const ChatMessageContent({super.key, required this.item});
@override
State<ChatMessageContent> createState() => _ChatMessageContentState();
}
class _ChatMessageContentState extends State<ChatMessageContent> {
@override
Widget build(BuildContext context) {
if (item.type == 'm.text') {
final feColor = Theme.of(context).colorScheme.onSurface.withOpacity(0.65);
final waitingKeyHint = Row(
children: [
Icon(Icons.key, color: feColor, size: 16),
const SizedBox(width: 4),
Expanded(
child: Text(
AppLocalizations.of(context)!.chatMessageUnableDecryptWaiting,
style: TextStyle(color: feColor),
),
),
],
);
final missingKeyHint = Row(
children: [
Icon(Icons.key_off_outlined, color: feColor, size: 16),
const SizedBox(width: 4),
Expanded(
child: Text(
AppLocalizations.of(context)!.chatMessageUnableDecryptMissing,
style: TextStyle(color: feColor),
),
),
],
);
if (widget.item.type == 'm.text') {
String? content;
switch (widget.item.decodedContent['algorithm']) {
case 'plain':
content = widget.item.decodedContent['value'];
case 'aes':
final keypair = context.watch<KeypairProvider>();
if (keypair.keys[widget.item.decodedContent['keypair_id']] == null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (keypair.requestKey(
widget.item.decodedContent['keypair_id'],
widget.item.decodedContent['algorithm'],
widget.item.sender.account.externalId!,
)) {
}
});
} else {
content = keypair.decodeViaAESKey(
widget.item.decodedContent['keypair_id'],
widget.item.decodedContent['value'],
)!;
break;
}
if (keypair.requestingKeys.contains(widget.item.decodedContent['keypair_id'])) {
return waitingKeyHint.animate().swap(builder: (context, _) {
return missingKeyHint;
}, delay: 3000.ms);
}
}
if (content == null) {
return Row(
children: [
Icon(Icons.key_off, color: feColor, size: 16),
const SizedBox(width: 4),
Text(
AppLocalizations.of(context)!.chatMessageUnableDecryptUnsupported,
style: TextStyle(color: feColor),
),
],
);
}
return Markdown(
data: item.decodedContent['value'],
data: content,
shrinkWrap: true,
selectable: true,
physics: const NeverScrollableScrollPhysics(),

View File

@ -9,6 +9,7 @@ import 'package:provider/provider.dart';
import 'package:solian/models/message.dart';
import 'package:solian/models/post.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/providers/keypair.dart';
import 'package:solian/utils/services_url.dart';
import 'package:solian/widgets/exts.dart';
import 'package:solian/widgets/posts/attachment_editor.dart';
@ -19,11 +20,13 @@ class ChatMessageEditor extends StatefulWidget {
final String realm;
final Message? editing;
final Message? replying;
final bool isEncrypted;
final Function? onReset;
const ChatMessageEditor({
super.key,
required this.channel,
required this.isEncrypted,
this.realm = 'global',
this.editing,
this.replying,
@ -55,8 +58,19 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
);
}
Map<String, dynamic> buildContentBody(String content, {String algorithm = 'plain'}) {
return {'value': content, 'algorithm': algorithm};
Map<String, dynamic> buildContentBody(String content) {
final keypair = context.read<KeypairProvider>();
if (keypair.activeKeyId == null || keypair.keys[keypair.activeKeyId] == null) {
final kp = keypair.generateAESKey();
keypair.setActiveKey(kp.id);
return buildContentBody(content);
}
return {
'value': widget.isEncrypted ? keypair.encodeViaAESKey(keypair.activeKeyId!, content) : content,
'keypair_id': widget.isEncrypted ? keypair.activeKeyId : null,
'algorithm': widget.isEncrypted ? 'aes' : 'plain',
};
}
Future<void> sendMessage(BuildContext context) async {
@ -214,7 +228,9 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
autocorrect: true,
keyboardType: TextInputType.text,
decoration: InputDecoration.collapsed(
hintText: AppLocalizations.of(context)!.chatMessagePlaceholder,
hintText: widget.isEncrypted
? AppLocalizations.of(context)!.chatMessageEncryptedPlaceholder
: AppLocalizations.of(context)!.chatMessagePlaceholder,
),
onSubmitted: (_) => sendMessage(context),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),