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.

185 lines
5.4 KiB

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