mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-17 01:48:40 +00:00
split signal functions into multiple files
This commit is contained in:
parent
9efdf26444
commit
2eb9fd2f00
11 changed files with 262 additions and 246 deletions
|
|
@ -21,14 +21,14 @@ import 'package:twonly/src/services/api/utils.dart';
|
||||||
import 'package:twonly/src/services/api/media_received.dart';
|
import 'package:twonly/src/services/api/media_received.dart';
|
||||||
import 'package:twonly/src/services/api/media_send.dart';
|
import 'package:twonly/src/services/api/media_send.dart';
|
||||||
import 'package:twonly/src/services/api/server_messages.dart';
|
import 'package:twonly/src/services/api/server_messages.dart';
|
||||||
|
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||||
|
import 'package:twonly/src/services/signal/utils.signal.dart';
|
||||||
import 'package:twonly/src/utils/hive.dart';
|
import 'package:twonly/src/utils/hive.dart';
|
||||||
import 'package:twonly/src/services/fcm.service.dart';
|
import 'package:twonly/src/services/fcm.service.dart';
|
||||||
import 'package:twonly/src/services/flame.service.dart';
|
import 'package:twonly/src/services/flame.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
// ignore: library_prefixes
|
|
||||||
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
|
||||||
import 'package:web_socket_channel/io.dart';
|
import 'package:web_socket_channel/io.dart';
|
||||||
// ignore: implementation_imports
|
// ignore: implementation_imports
|
||||||
import 'package:libsignal_protocol_dart/src/ecc/ed25519.dart';
|
import 'package:libsignal_protocol_dart/src/ecc/ed25519.dart';
|
||||||
|
|
@ -306,7 +306,7 @@ class ApiService {
|
||||||
|
|
||||||
Future authenticate() async {
|
Future authenticate() async {
|
||||||
if (isAuthenticated) return;
|
if (isAuthenticated) return;
|
||||||
if (await SignalHelper.getSignalIdentity() == null) {
|
if (await getSignalIdentity() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -329,7 +329,7 @@ class ApiService {
|
||||||
|
|
||||||
final challenge = result.value.authchallenge;
|
final challenge = result.value.authchallenge;
|
||||||
|
|
||||||
final privKey = await SignalHelper.getPrivateKey();
|
final privKey = await getSignalPrivateIdentityKey();
|
||||||
if (privKey == null) return;
|
if (privKey == null) return;
|
||||||
final random = getRandomUint8List(32);
|
final random = getRandomUint8List(32);
|
||||||
final signature = sign(privKey.serialize(), challenge, random);
|
final signature = sign(privKey.serialize(), challenge, random);
|
||||||
|
|
@ -358,13 +358,12 @@ class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result> register(String username, String? inviteCode) async {
|
Future<Result> register(String username, String? inviteCode) async {
|
||||||
final signalIdentity = await SignalHelper.getSignalIdentity();
|
final signalIdentity = await getSignalIdentity();
|
||||||
if (signalIdentity == null) {
|
if (signalIdentity == null) {
|
||||||
return Result.error(ErrorCode.InternalError);
|
return Result.error(ErrorCode.InternalError);
|
||||||
}
|
}
|
||||||
|
|
||||||
final signalStore =
|
final signalStore = await getSignalStoreFromIdentity(signalIdentity);
|
||||||
await SignalHelper.getSignalStoreFromIdentity(signalIdentity);
|
|
||||||
|
|
||||||
final signedPreKey = (await signalStore.loadSignedPreKeys())[0];
|
final signedPreKey = (await signalStore.loadSignedPreKeys())[0];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,9 @@ import 'package:twonly/src/database/tables/messages_table.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/model/json/userdata.dart';
|
import 'package:twonly/src/model/json/userdata.dart';
|
||||||
import 'package:twonly/src/services/api/utils.dart';
|
import 'package:twonly/src/services/api/utils.dart';
|
||||||
|
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
||||||
import 'package:twonly/src/utils/hive.dart';
|
import 'package:twonly/src/utils/hive.dart';
|
||||||
import 'package:twonly/src/services/notification.service.dart';
|
import 'package:twonly/src/services/notification.service.dart';
|
||||||
// ignore: library_prefixes
|
|
||||||
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
final lockSendingMessages = Mutex();
|
final lockSendingMessages = Mutex();
|
||||||
|
|
@ -138,7 +137,7 @@ Future<(String, RetransmitMessage)?> encryptMessage(
|
||||||
{PushKind? pushKind}) async {
|
{PushKind? pushKind}) async {
|
||||||
return await lockSendingMessages
|
return await lockSendingMessages
|
||||||
.protect<(String, RetransmitMessage)?>(() async {
|
.protect<(String, RetransmitMessage)?>(() async {
|
||||||
Uint8List? bytes = await SignalHelper.encryptMessage(msg, userId);
|
Uint8List? bytes = await signalEncryptMessage(userId, msg);
|
||||||
|
|
||||||
if (bytes == null) {
|
if (bytes == null) {
|
||||||
Logger("api.dart").shout("Error encryption message!");
|
Logger("api.dart").shout("Error encryption message!");
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/services/api/utils.dart';
|
import 'package:twonly/src/services/api/utils.dart';
|
||||||
import 'package:twonly/src/services/api/media_received.dart';
|
import 'package:twonly/src/services/api/media_received.dart';
|
||||||
import 'package:twonly/src/services/notification.service.dart';
|
import 'package:twonly/src/services/notification.service.dart';
|
||||||
|
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
||||||
|
import 'package:twonly/src/services/signal/utils.signal.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
// ignore: library_prefixes
|
|
||||||
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
|
||||||
|
|
||||||
final lockHandleServerMessage = Mutex();
|
final lockHandleServerMessage = Mutex();
|
||||||
|
|
||||||
|
|
@ -53,7 +53,7 @@ Future handleServerMessage(server.ServerToClient msg) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
||||||
MessageJson? message = await SignalHelper.getDecryptedText(fromUserId, body);
|
MessageJson? message = await signalDecryptMessage(fromUserId, body);
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
Logger("server_messages")
|
Logger("server_messages")
|
||||||
.info("Got invalid cipher text from $fromUserId. Deleting it.");
|
.info("Got invalid cipher text from $fromUserId. Deleting it.");
|
||||||
|
|
@ -264,7 +264,7 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<client.Response> handleRequestNewPreKey() async {
|
Future<client.Response> handleRequestNewPreKey() async {
|
||||||
List<PreKeyRecord> localPreKeys = await SignalHelper.getPreKeys();
|
List<PreKeyRecord> localPreKeys = await signalGetPreKeys();
|
||||||
|
|
||||||
List<client.Response_PreKey> prekeysList = [];
|
List<client.Response_PreKey> prekeysList = [];
|
||||||
for (int i = 0; i < localPreKeys.length; i++) {
|
for (int i = 0; i < localPreKeys.length; i++) {
|
||||||
|
|
|
||||||
64
lib/src/services/signal/encryption.signal.dart
Normal file
64
lib/src/services/signal/encryption.signal.dart
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
|
import 'package:twonly/src/database/signal/connect_signal_protocol_store.dart';
|
||||||
|
import 'package:twonly/src/services/signal/utils.signal.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
|
Future<Uint8List?> signalEncryptMessage(int target, MessageJson msg) async {
|
||||||
|
try {
|
||||||
|
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
|
||||||
|
|
||||||
|
SessionCipher session = SessionCipher.fromStore(
|
||||||
|
signalStore, SignalProtocolAddress(target.toString(), defaultDeviceId));
|
||||||
|
|
||||||
|
final ciphertext = await session.encrypt(
|
||||||
|
Uint8List.fromList(gzip.encode(utf8.encode(jsonEncode(msg.toJson())))));
|
||||||
|
|
||||||
|
var b = BytesBuilder();
|
||||||
|
b.add(ciphertext.serialize());
|
||||||
|
b.add(intToBytes(ciphertext.getType()));
|
||||||
|
|
||||||
|
return b.takeBytes();
|
||||||
|
} catch (e) {
|
||||||
|
Logger("utils/signal").shout(e.toString());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<MessageJson?> signalDecryptMessage(int source, Uint8List msg) async {
|
||||||
|
try {
|
||||||
|
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
|
||||||
|
|
||||||
|
SessionCipher session = SessionCipher.fromStore(
|
||||||
|
signalStore, SignalProtocolAddress(source.toString(), defaultDeviceId));
|
||||||
|
|
||||||
|
List<Uint8List>? msgs = removeLastXBytes(msg, 4);
|
||||||
|
if (msgs == null) {
|
||||||
|
Logger("utils/signal").shout("Message requires at least 4 bytes.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Uint8List body = msgs[0];
|
||||||
|
int type = bytesToInt(msgs[1]);
|
||||||
|
Uint8List plaintext;
|
||||||
|
if (type == CiphertextMessage.prekeyType) {
|
||||||
|
PreKeySignalMessage pre = PreKeySignalMessage(body);
|
||||||
|
plaintext = await session.decrypt(pre);
|
||||||
|
} else if (type == CiphertextMessage.whisperType) {
|
||||||
|
SignalMessage signalMsg = SignalMessage.fromSerialized(body);
|
||||||
|
plaintext = await session.decryptFromSignal(signalMsg);
|
||||||
|
} else {
|
||||||
|
Logger("utils/signal").shout("Type not known: $type");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
MessageJson dectext =
|
||||||
|
MessageJson.fromJson(jsonDecode(utf8.decode(gzip.decode(plaintext))));
|
||||||
|
return dectext;
|
||||||
|
} catch (e) {
|
||||||
|
Logger("utils/signal").shout(e.toString());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
62
lib/src/services/signal/identity.signal.dart
Normal file
62
lib/src/services/signal/identity.signal.dart
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:twonly/src/model/json/signal_identity.dart';
|
||||||
|
import 'package:twonly/src/database/signal/connect_signal_protocol_store.dart';
|
||||||
|
|
||||||
|
const int defaultDeviceId = 1;
|
||||||
|
|
||||||
|
Future<ECPrivateKey?> getSignalPrivateIdentityKey() async {
|
||||||
|
final signalIdentity = await getSignalIdentity();
|
||||||
|
if (signalIdentity == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final IdentityKeyPair identityKeyPair =
|
||||||
|
IdentityKeyPair.fromSerialized(signalIdentity.identityKeyPairU8List);
|
||||||
|
|
||||||
|
return identityKeyPair.getPrivateKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<SignalIdentity?> getSignalIdentity() async {
|
||||||
|
try {
|
||||||
|
final storage = FlutterSecureStorage();
|
||||||
|
final signalIdentityJson = await storage.read(key: "signal_identity");
|
||||||
|
if (signalIdentityJson == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return SignalIdentity.fromJson(jsonDecode(signalIdentityJson));
|
||||||
|
} catch (e) {
|
||||||
|
Logger("signal.dart/getSignalIdentity").shout(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future createIfNotExistsSignalIdentity() async {
|
||||||
|
final storage = FlutterSecureStorage();
|
||||||
|
|
||||||
|
final signalIdentity = await storage.read(key: "signal_identity");
|
||||||
|
if (signalIdentity != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final identityKeyPair = generateIdentityKeyPair();
|
||||||
|
final registrationId = generateRegistrationId(true);
|
||||||
|
|
||||||
|
ConnectSignalProtocolStore signalStore =
|
||||||
|
ConnectSignalProtocolStore(identityKeyPair, registrationId);
|
||||||
|
|
||||||
|
final signedPreKey = generateSignedPreKey(identityKeyPair, defaultDeviceId);
|
||||||
|
|
||||||
|
await signalStore.signedPreKeyStore
|
||||||
|
.storeSignedPreKey(signedPreKey.id, signedPreKey);
|
||||||
|
|
||||||
|
final storedSignalIdentity = SignalIdentity(
|
||||||
|
identityKeyPairU8List: identityKeyPair.serialize(),
|
||||||
|
registrationId: registrationId,
|
||||||
|
);
|
||||||
|
|
||||||
|
await storage.write(
|
||||||
|
key: "signal_identity", value: jsonEncode(storedSignalIdentity));
|
||||||
|
}
|
||||||
92
lib/src/services/signal/session.signal.dart
Normal file
92
lib/src/services/signal/session.signal.dart
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
|
import 'package:twonly/src/model/json/userdata.dart';
|
||||||
|
import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart';
|
||||||
|
import 'package:twonly/src/database/signal/connect_signal_protocol_store.dart';
|
||||||
|
import 'package:twonly/src/services/signal/utils.signal.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
|
const int defaultDeviceId = 1;
|
||||||
|
|
||||||
|
Future<bool> createNewSignalSession(Response_UserData userData) async {
|
||||||
|
final int userId = userData.userId.toInt();
|
||||||
|
|
||||||
|
SignalProtocolAddress targetAddress =
|
||||||
|
SignalProtocolAddress(userId.toString(), defaultDeviceId);
|
||||||
|
|
||||||
|
SignalProtocolStore? signalStore = await getSignalStore();
|
||||||
|
if (signalStore == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SessionBuilder sessionBuilder =
|
||||||
|
SessionBuilder.fromSignalStore(signalStore, targetAddress);
|
||||||
|
|
||||||
|
ECPublicKey? tempPrePublicKey;
|
||||||
|
int? tempPreKeyId;
|
||||||
|
if (userData.prekeys.isNotEmpty) {
|
||||||
|
tempPrePublicKey = Curve.decodePoint(
|
||||||
|
DjbECPublicKey(Uint8List.fromList(userData.prekeys.first.prekey))
|
||||||
|
.serialize(),
|
||||||
|
1);
|
||||||
|
tempPreKeyId = userData.prekeys.first.id.toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
int tempSignedPreKeyId = userData.signedPrekeyId.toInt();
|
||||||
|
|
||||||
|
ECPublicKey? tempSignedPreKeyPublic = Curve.decodePoint(
|
||||||
|
DjbECPublicKey(Uint8List.fromList(userData.signedPrekey)).serialize(), 1);
|
||||||
|
|
||||||
|
Uint8List? tempSignedPreKeySignature =
|
||||||
|
Uint8List.fromList(userData.signedPrekeySignature);
|
||||||
|
|
||||||
|
IdentityKey tempIdentityKey = IdentityKey(Curve.decodePoint(
|
||||||
|
DjbECPublicKey(Uint8List.fromList(userData.publicIdentityKey))
|
||||||
|
.serialize(),
|
||||||
|
1));
|
||||||
|
|
||||||
|
PreKeyBundle preKeyBundle = PreKeyBundle(
|
||||||
|
userData.userId.toInt(),
|
||||||
|
1,
|
||||||
|
tempPreKeyId,
|
||||||
|
tempPrePublicKey,
|
||||||
|
tempSignedPreKeyId,
|
||||||
|
tempSignedPreKeyPublic,
|
||||||
|
tempSignedPreKeySignature,
|
||||||
|
tempIdentityKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sessionBuilder.processPreKeyBundle(preKeyBundle);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
Log.error("could not process pre key bundle: $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Fingerprint?> generateSessionFingerPrint(int target) async {
|
||||||
|
ConnectSignalProtocolStore? signalStore = await getSignalStore();
|
||||||
|
UserData? user = await getUser();
|
||||||
|
if (signalStore == null || user == null) return null;
|
||||||
|
try {
|
||||||
|
IdentityKey? targetIdentity = await signalStore
|
||||||
|
.getIdentity(SignalProtocolAddress(target.toString(), defaultDeviceId));
|
||||||
|
if (targetIdentity != null) {
|
||||||
|
final generator = NumericFingerprintGenerator(5200);
|
||||||
|
final localFingerprint = generator.createFor(
|
||||||
|
1,
|
||||||
|
Uint8List.fromList([user.userId.toInt()]),
|
||||||
|
(await signalStore.getIdentityKeyPair()).getPublicKey(),
|
||||||
|
Uint8List.fromList([target.toInt()]),
|
||||||
|
targetIdentity,
|
||||||
|
);
|
||||||
|
|
||||||
|
return localFingerprint;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
lib/src/services/signal/utils.signal.dart
Normal file
29
lib/src/services/signal/utils.signal.dart
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
|
import 'package:twonly/src/model/json/signal_identity.dart';
|
||||||
|
import 'package:twonly/src/database/signal/connect_signal_protocol_store.dart';
|
||||||
|
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||||
|
|
||||||
|
const int defaultDeviceId = 1;
|
||||||
|
|
||||||
|
Future<ConnectSignalProtocolStore?> getSignalStore() async {
|
||||||
|
return await getSignalStoreFromIdentity((await getSignalIdentity())!);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ConnectSignalProtocolStore> getSignalStoreFromIdentity(
|
||||||
|
SignalIdentity signalIdentity) async {
|
||||||
|
final IdentityKeyPair identityKeyPair =
|
||||||
|
IdentityKeyPair.fromSerialized(signalIdentity.identityKeyPairU8List);
|
||||||
|
|
||||||
|
return ConnectSignalProtocolStore(
|
||||||
|
identityKeyPair, signalIdentity.registrationId.toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<PreKeyRecord>> signalGetPreKeys() async {
|
||||||
|
final preKeys = generatePreKeys(0, 200);
|
||||||
|
final signalStore = await getSignalStore();
|
||||||
|
if (signalStore == null) return [];
|
||||||
|
for (final p in preKeys) {
|
||||||
|
await signalStore.preKeyStore.storePreKey(p.id, p);
|
||||||
|
}
|
||||||
|
return preKeys;
|
||||||
|
}
|
||||||
|
|
@ -1,229 +0,0 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
|
||||||
import 'package:twonly/src/model/json/signal_identity.dart';
|
|
||||||
import 'package:twonly/src/model/json/userdata.dart';
|
|
||||||
import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart';
|
|
||||||
import 'package:twonly/src/database/signal/connect_signal_protocol_store.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
|
||||||
|
|
||||||
const int defaultDeviceId = 1;
|
|
||||||
|
|
||||||
Future<ECPrivateKey?> getPrivateKey() async {
|
|
||||||
final signalIdentity = await getSignalIdentity();
|
|
||||||
if (signalIdentity == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final IdentityKeyPair identityKeyPair =
|
|
||||||
IdentityKeyPair.fromSerialized(signalIdentity.identityKeyPairU8List);
|
|
||||||
|
|
||||||
return identityKeyPair.getPrivateKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> addNewContact(Response_UserData userData) async {
|
|
||||||
final int userId = userData.userId.toInt();
|
|
||||||
|
|
||||||
SignalProtocolAddress targetAddress =
|
|
||||||
SignalProtocolAddress(userId.toString(), defaultDeviceId);
|
|
||||||
|
|
||||||
SignalProtocolStore? signalStore = await getSignalStore();
|
|
||||||
if (signalStore == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SessionBuilder sessionBuilder =
|
|
||||||
SessionBuilder.fromSignalStore(signalStore, targetAddress);
|
|
||||||
|
|
||||||
ECPublicKey? tempPrePublicKey;
|
|
||||||
int? tempPreKeyId;
|
|
||||||
if (userData.prekeys.isNotEmpty) {
|
|
||||||
tempPrePublicKey = Curve.decodePoint(
|
|
||||||
DjbECPublicKey(Uint8List.fromList(userData.prekeys.first.prekey))
|
|
||||||
.serialize(),
|
|
||||||
1);
|
|
||||||
tempPreKeyId = userData.prekeys.first.id.toInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
int tempSignedPreKeyId = userData.signedPrekeyId.toInt();
|
|
||||||
|
|
||||||
ECPublicKey? tempSignedPreKeyPublic = Curve.decodePoint(
|
|
||||||
DjbECPublicKey(Uint8List.fromList(userData.signedPrekey)).serialize(), 1);
|
|
||||||
|
|
||||||
Uint8List? tempSignedPreKeySignature =
|
|
||||||
Uint8List.fromList(userData.signedPrekeySignature);
|
|
||||||
|
|
||||||
IdentityKey tempIdentityKey = IdentityKey(Curve.decodePoint(
|
|
||||||
DjbECPublicKey(Uint8List.fromList(userData.publicIdentityKey))
|
|
||||||
.serialize(),
|
|
||||||
1));
|
|
||||||
|
|
||||||
PreKeyBundle preKeyBundle = PreKeyBundle(
|
|
||||||
userData.userId.toInt(),
|
|
||||||
1,
|
|
||||||
tempPreKeyId,
|
|
||||||
tempPrePublicKey,
|
|
||||||
tempSignedPreKeyId,
|
|
||||||
tempSignedPreKeyPublic,
|
|
||||||
tempSignedPreKeySignature,
|
|
||||||
tempIdentityKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await sessionBuilder.processPreKeyBundle(preKeyBundle);
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
Logger("signal_helper").shout("Error: $e");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ConnectSignalProtocolStore?> getSignalStore() async {
|
|
||||||
return await getSignalStoreFromIdentity((await getSignalIdentity())!);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SignalIdentity?> getSignalIdentity() async {
|
|
||||||
try {
|
|
||||||
final storage = FlutterSecureStorage();
|
|
||||||
final signalIdentityJson = await storage.read(key: "signal_identity");
|
|
||||||
if (signalIdentityJson == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return SignalIdentity.fromJson(jsonDecode(signalIdentityJson));
|
|
||||||
} catch (e) {
|
|
||||||
Logger("signal.dart/getSignalIdentity").shout(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ConnectSignalProtocolStore> getSignalStoreFromIdentity(
|
|
||||||
SignalIdentity signalIdentity) async {
|
|
||||||
final IdentityKeyPair identityKeyPair =
|
|
||||||
IdentityKeyPair.fromSerialized(signalIdentity.identityKeyPairU8List);
|
|
||||||
|
|
||||||
return ConnectSignalProtocolStore(
|
|
||||||
identityKeyPair, signalIdentity.registrationId.toInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<PreKeyRecord>> getPreKeys() async {
|
|
||||||
final preKeys = generatePreKeys(0, 200);
|
|
||||||
final signalStore = await getSignalStore();
|
|
||||||
if (signalStore == null) return [];
|
|
||||||
for (final p in preKeys) {
|
|
||||||
await signalStore.preKeyStore.storePreKey(p.id, p);
|
|
||||||
}
|
|
||||||
return preKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future createIfNotExistsSignalIdentity() async {
|
|
||||||
final storage = FlutterSecureStorage();
|
|
||||||
|
|
||||||
final signalIdentity = await storage.read(key: "signal_identity");
|
|
||||||
if (signalIdentity != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final identityKeyPair = generateIdentityKeyPair();
|
|
||||||
final registrationId = generateRegistrationId(true);
|
|
||||||
|
|
||||||
ConnectSignalProtocolStore signalStore =
|
|
||||||
ConnectSignalProtocolStore(identityKeyPair, registrationId);
|
|
||||||
|
|
||||||
final signedPreKey = generateSignedPreKey(identityKeyPair, defaultDeviceId);
|
|
||||||
|
|
||||||
await signalStore.signedPreKeyStore
|
|
||||||
.storeSignedPreKey(signedPreKey.id, signedPreKey);
|
|
||||||
|
|
||||||
final storedSignalIdentity = SignalIdentity(
|
|
||||||
identityKeyPairU8List: identityKeyPair.serialize(),
|
|
||||||
registrationId: registrationId,
|
|
||||||
);
|
|
||||||
|
|
||||||
await storage.write(
|
|
||||||
key: "signal_identity", value: jsonEncode(storedSignalIdentity));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Fingerprint?> generateSessionFingerPrint(int target) async {
|
|
||||||
ConnectSignalProtocolStore? signalStore = await getSignalStore();
|
|
||||||
UserData? user = await getUser();
|
|
||||||
if (signalStore == null || user == null) return null;
|
|
||||||
try {
|
|
||||||
IdentityKey? targetIdentity = await signalStore
|
|
||||||
.getIdentity(SignalProtocolAddress(target.toString(), defaultDeviceId));
|
|
||||||
if (targetIdentity != null) {
|
|
||||||
final generator = NumericFingerprintGenerator(5200);
|
|
||||||
final localFingerprint = generator.createFor(
|
|
||||||
1,
|
|
||||||
Uint8List.fromList([user.userId.toInt()]),
|
|
||||||
(await signalStore.getIdentityKeyPair()).getPublicKey(),
|
|
||||||
Uint8List.fromList([target.toInt()]),
|
|
||||||
targetIdentity,
|
|
||||||
);
|
|
||||||
|
|
||||||
return localFingerprint;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Uint8List?> encryptMessage(MessageJson msg, int target) async {
|
|
||||||
try {
|
|
||||||
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
|
|
||||||
|
|
||||||
SessionCipher session = SessionCipher.fromStore(
|
|
||||||
signalStore, SignalProtocolAddress(target.toString(), defaultDeviceId));
|
|
||||||
|
|
||||||
final ciphertext = await session.encrypt(
|
|
||||||
Uint8List.fromList(gzip.encode(utf8.encode(jsonEncode(msg.toJson())))));
|
|
||||||
|
|
||||||
var b = BytesBuilder();
|
|
||||||
b.add(ciphertext.serialize());
|
|
||||||
b.add(intToBytes(ciphertext.getType()));
|
|
||||||
|
|
||||||
return b.takeBytes();
|
|
||||||
} catch (e) {
|
|
||||||
Logger("utils/signal").shout(e.toString());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<MessageJson?> getDecryptedText(int source, Uint8List msg) async {
|
|
||||||
try {
|
|
||||||
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
|
|
||||||
|
|
||||||
SessionCipher session = SessionCipher.fromStore(
|
|
||||||
signalStore, SignalProtocolAddress(source.toString(), defaultDeviceId));
|
|
||||||
|
|
||||||
List<Uint8List>? msgs = removeLastXBytes(msg, 4);
|
|
||||||
if (msgs == null) {
|
|
||||||
Logger("utils/signal").shout("Message requires at least 4 bytes.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Uint8List body = msgs[0];
|
|
||||||
int type = bytesToInt(msgs[1]);
|
|
||||||
Uint8List plaintext;
|
|
||||||
if (type == CiphertextMessage.prekeyType) {
|
|
||||||
PreKeySignalMessage pre = PreKeySignalMessage(body);
|
|
||||||
plaintext = await session.decrypt(pre);
|
|
||||||
} else if (type == CiphertextMessage.whisperType) {
|
|
||||||
SignalMessage signalMsg = SignalMessage.fromSerialized(body);
|
|
||||||
plaintext = await session.decryptFromSignal(signalMsg);
|
|
||||||
} else {
|
|
||||||
Logger("utils/signal").shout("Type not known: $type");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
MessageJson dectext =
|
|
||||||
MessageJson.fromJson(jsonDecode(utf8.decode(gzip.decode(plaintext))));
|
|
||||||
return dectext;
|
|
||||||
} catch (e) {
|
|
||||||
Logger("utils/signal").shout(e.toString());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -17,7 +17,7 @@ import 'package:twonly/src/views/components/initialsavatar.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
// ignore: library_prefixes
|
// ignore: library_prefixes
|
||||||
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
import 'package:twonly/src/services/signal/utils.signal.dart' as SignalHelper;
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/settings/subscription/subscription_view.dart';
|
import 'package:twonly/src/views/settings/subscription/subscription_view.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/signal.dart';
|
import 'package:twonly/src/services/signal/utils.signal.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class ContactVerifyView extends StatefulWidget {
|
class ContactVerifyView extends StatefulWidget {
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ import 'package:logging/logging.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||||
import 'package:twonly/src/views/components/alert_dialog.dart';
|
import 'package:twonly/src/views/components/alert_dialog.dart';
|
||||||
import 'package:twonly/src/model/json/userdata.dart';
|
import 'package:twonly/src/model/json/userdata.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/signal.dart';
|
|
||||||
|
|
||||||
class RegisterView extends StatefulWidget {
|
class RegisterView extends StatefulWidget {
|
||||||
const RegisterView({super.key, required this.callbackOnSuccess});
|
const RegisterView({super.key, required this.callbackOnSuccess});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue