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.

197 lines
5.3 KiB

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