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.

189 lines
5.5 KiB

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