Encrypted messaging app
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

195 lines
5.2 KiB

  1. import 'dart:convert';
  2. import 'dart:typed_data';
  3. import 'package:pointycastle/export.dart';
  4. import '/models/conversation_users.dart';
  5. import '/models/conversations.dart';
  6. import '/models/my_profile.dart';
  7. import '/models/friends.dart';
  8. import '/utils/encryption/aes_helper.dart';
  9. import '/utils/encryption/crypto_utils.dart';
  10. import '/utils/storage/database.dart';
  11. import '/utils/strings.dart';
  12. const messageTypeReceiver = 'receiver';
  13. const messageTypeSender = 'sender';
  14. Future<List<Message>> getMessagesForThread(Conversation conversation) async {
  15. final db = await getDatabaseConnection();
  16. final List<Map<String, dynamic>> maps = await db.rawQuery(
  17. '''
  18. SELECT * FROM messages WHERE association_key IN (
  19. SELECT association_key FROM conversation_users WHERE conversation_id = ?
  20. )
  21. ORDER BY created_at DESC;
  22. ''',
  23. [conversation.id]
  24. );
  25. return List.generate(maps.length, (i) {
  26. return Message(
  27. id: maps[i]['id'],
  28. symmetricKey: maps[i]['symmetric_key'],
  29. userSymmetricKey: maps[i]['user_symmetric_key'],
  30. data: maps[i]['data'],
  31. senderId: maps[i]['sender_id'],
  32. senderUsername: maps[i]['sender_username'],
  33. associationKey: maps[i]['association_key'],
  34. createdAt: maps[i]['created_at'],
  35. failedToSend: maps[i]['failed_to_send'] == 1,
  36. );
  37. });
  38. }
  39. class Message {
  40. String id;
  41. String symmetricKey;
  42. String userSymmetricKey;
  43. String data;
  44. String senderId;
  45. String senderUsername;
  46. String associationKey;
  47. String createdAt;
  48. bool failedToSend;
  49. Message({
  50. required this.id,
  51. required this.symmetricKey,
  52. required this.userSymmetricKey,
  53. required this.data,
  54. required this.senderId,
  55. required this.senderUsername,
  56. required this.associationKey,
  57. required this.createdAt,
  58. required this.failedToSend,
  59. });
  60. factory Message.fromJson(Map<String, dynamic> json, RSAPrivateKey privKey) {
  61. var userSymmetricKey = CryptoUtils.rsaDecrypt(
  62. base64.decode(json['symmetric_key']),
  63. privKey,
  64. );
  65. var symmetricKey = AesHelper.aesDecrypt(
  66. userSymmetricKey,
  67. base64.decode(json['message_data']['symmetric_key']),
  68. );
  69. var senderId = AesHelper.aesDecrypt(
  70. base64.decode(symmetricKey),
  71. base64.decode(json['message_data']['sender_id']),
  72. );
  73. var data = AesHelper.aesDecrypt(
  74. base64.decode(symmetricKey),
  75. base64.decode(json['message_data']['data']),
  76. );
  77. return Message(
  78. id: json['id'],
  79. symmetricKey: symmetricKey,
  80. userSymmetricKey: base64.encode(userSymmetricKey),
  81. data: data,
  82. senderId: senderId,
  83. senderUsername: 'Unknown',
  84. associationKey: json['association_key'],
  85. createdAt: json['created_at'],
  86. failedToSend: false,
  87. );
  88. }
  89. Future<String> toJson(Conversation conversation, String messageDataId) async {
  90. MyProfile profile = await MyProfile.getProfile();
  91. if (profile.publicKey == null) {
  92. throw Exception('Could not get profile.publicKey');
  93. }
  94. RSAPublicKey publicKey = profile.publicKey!;
  95. final userSymmetricKey = AesHelper.deriveKey(generateRandomString(32));
  96. final symmetricKey = AesHelper.deriveKey(generateRandomString(32));
  97. List<Map<String, String>> messages = [];
  98. String id = '';
  99. List<ConversationUser> conversationUsers = await getConversationUsers(conversation);
  100. for (var i = 0; i < conversationUsers.length; i++) {
  101. ConversationUser user = conversationUsers[i];
  102. if (profile.id == user.userId) {
  103. id = user.id;
  104. messages.add({
  105. 'message_data_id': messageDataId,
  106. 'symmetric_key': base64.encode(CryptoUtils.rsaEncrypt(
  107. userSymmetricKey,
  108. publicKey,
  109. )),
  110. 'association_key': user.associationKey,
  111. });
  112. continue;
  113. }
  114. Friend friend = await getFriendByFriendId(user.userId);
  115. RSAPublicKey friendPublicKey = CryptoUtils.rsaPublicKeyFromPem(friend.asymmetricPublicKey);
  116. messages.add({
  117. 'message_data_id': messageDataId,
  118. 'symmetric_key': base64.encode(CryptoUtils.rsaEncrypt(
  119. userSymmetricKey,
  120. friendPublicKey,
  121. )),
  122. 'association_key': user.associationKey,
  123. });
  124. }
  125. Map<String, String> messageData = {
  126. 'id': messageDataId,
  127. 'data': AesHelper.aesEncrypt(symmetricKey, Uint8List.fromList(data.codeUnits)),
  128. 'sender_id': AesHelper.aesEncrypt(symmetricKey, Uint8List.fromList(senderId.codeUnits)),
  129. 'symmetric_key': AesHelper.aesEncrypt(
  130. userSymmetricKey,
  131. Uint8List.fromList(base64.encode(symmetricKey).codeUnits),
  132. ),
  133. };
  134. return jsonEncode(<String, dynamic>{
  135. 'message_data': messageData,
  136. 'message': messages,
  137. });
  138. }
  139. Map<String, dynamic> toMap() {
  140. return {
  141. 'id': id,
  142. 'symmetric_key': symmetricKey,
  143. 'user_symmetric_key': userSymmetricKey,
  144. 'data': data,
  145. 'sender_id': senderId,
  146. 'sender_username': senderUsername,
  147. 'association_key': associationKey,
  148. 'created_at': createdAt,
  149. 'failed_to_send': failedToSend ? 1 : 0,
  150. };
  151. }
  152. @override
  153. String toString() {
  154. return '''
  155. id: $id
  156. data: $data
  157. senderId: $senderId
  158. senderUsername: $senderUsername
  159. associationKey: $associationKey
  160. createdAt: $createdAt
  161. ''';
  162. }
  163. }