import 'dart:convert'; import 'dart:typed_data'; import 'package:pointycastle/export.dart'; import '/models/conversation_users.dart'; import '/models/conversations.dart'; import '/models/my_profile.dart'; import '/models/friends.dart'; import '/utils/encryption/aes_helper.dart'; import '/utils/encryption/crypto_utils.dart'; import '/utils/storage/database.dart'; import '/utils/strings.dart'; const messageTypeReceiver = 'receiver'; const messageTypeSender = 'sender'; Future> getMessagesForThread(Conversation conversation) async { final db = await getDatabaseConnection(); final List> maps = await db.rawQuery( ''' SELECT * FROM messages WHERE association_key IN ( SELECT association_key FROM conversation_users WHERE conversation_id = ? ) ORDER BY created_at DESC; ''', [conversation.id] ); return List.generate(maps.length, (i) { return Message( id: maps[i]['id'], symmetricKey: maps[i]['symmetric_key'], userSymmetricKey: maps[i]['user_symmetric_key'], data: maps[i]['data'], senderId: maps[i]['sender_id'], senderUsername: maps[i]['sender_username'], associationKey: maps[i]['association_key'], createdAt: maps[i]['created_at'], failedToSend: maps[i]['failed_to_send'] == 1, ); }); } class Message { String id; String symmetricKey; String userSymmetricKey; String data; String senderId; String senderUsername; String associationKey; String createdAt; bool failedToSend; Message({ required this.id, required this.symmetricKey, required this.userSymmetricKey, required this.data, required this.senderId, required this.senderUsername, required this.associationKey, required this.createdAt, required this.failedToSend, }); factory Message.fromJson(Map json, RSAPrivateKey privKey) { var userSymmetricKey = CryptoUtils.rsaDecrypt( base64.decode(json['symmetric_key']), privKey, ); var symmetricKey = AesHelper.aesDecrypt( userSymmetricKey, base64.decode(json['message_data']['symmetric_key']), ); var senderId = AesHelper.aesDecrypt( base64.decode(symmetricKey), base64.decode(json['message_data']['sender_id']), ); var data = AesHelper.aesDecrypt( base64.decode(symmetricKey), base64.decode(json['message_data']['data']), ); return Message( id: json['id'], symmetricKey: symmetricKey, userSymmetricKey: base64.encode(userSymmetricKey), data: data, senderId: senderId, senderUsername: 'Unknown', associationKey: json['association_key'], createdAt: json['created_at'], failedToSend: false, ); } Future toJson(Conversation conversation, String messageDataId) async { MyProfile profile = await MyProfile.getProfile(); if (profile.publicKey == null) { throw Exception('Could not get profile.publicKey'); } RSAPublicKey publicKey = profile.publicKey!; final userSymmetricKey = AesHelper.deriveKey(generateRandomString(32)); final symmetricKey = AesHelper.deriveKey(generateRandomString(32)); List> messages = []; String id = ''; List conversationUsers = await getConversationUsers(conversation); for (var i = 0; i < conversationUsers.length; i++) { ConversationUser user = conversationUsers[i]; if (profile.id == user.userId) { id = user.id; messages.add({ 'message_data_id': messageDataId, 'symmetric_key': base64.encode(CryptoUtils.rsaEncrypt( userSymmetricKey, publicKey, )), 'association_key': user.associationKey, }); continue; } Friend friend = await getFriendByFriendId(user.userId); RSAPublicKey friendPublicKey = CryptoUtils.rsaPublicKeyFromPem(friend.asymmetricPublicKey); messages.add({ 'message_data_id': messageDataId, 'symmetric_key': base64.encode(CryptoUtils.rsaEncrypt( userSymmetricKey, friendPublicKey, )), 'association_key': user.associationKey, }); } Map messageData = { 'id': messageDataId, 'data': AesHelper.aesEncrypt(symmetricKey, Uint8List.fromList(data.codeUnits)), 'sender_id': AesHelper.aesEncrypt(symmetricKey, Uint8List.fromList(senderId.codeUnits)), 'symmetric_key': AesHelper.aesEncrypt( userSymmetricKey, Uint8List.fromList(base64.encode(symmetricKey).codeUnits), ), }; return jsonEncode({ 'message_data': messageData, 'message': messages, }); } Map toMap() { return { 'id': id, 'symmetric_key': symmetricKey, 'user_symmetric_key': userSymmetricKey, 'data': data, 'sender_id': senderId, 'sender_username': senderUsername, 'association_key': associationKey, 'created_at': createdAt, 'failed_to_send': failedToSend ? 1 : 0, }; } @override String toString() { return ''' id: $id data: $data senderId: $senderId senderUsername: $senderUsername associationKey: $associationKey createdAt: $createdAt '''; } }