split signal functions into multiple files

This commit is contained in:
otsmr 2025-05-30 17:50:26 +02:00
parent 9efdf26444
commit 2eb9fd2f00
11 changed files with 262 additions and 246 deletions

View file

@ -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_send.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/services/fcm.service.dart';
import 'package:twonly/src/services/flame.service.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.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';
// ignore: implementation_imports
import 'package:libsignal_protocol_dart/src/ecc/ed25519.dart';
@ -306,7 +306,7 @@ class ApiService {
Future authenticate() async {
if (isAuthenticated) return;
if (await SignalHelper.getSignalIdentity() == null) {
if (await getSignalIdentity() == null) {
return;
}
@ -329,7 +329,7 @@ class ApiService {
final challenge = result.value.authchallenge;
final privKey = await SignalHelper.getPrivateKey();
final privKey = await getSignalPrivateIdentityKey();
if (privKey == null) return;
final random = getRandomUint8List(32);
final signature = sign(privKey.serialize(), challenge, random);
@ -358,13 +358,12 @@ class ApiService {
}
Future<Result> register(String username, String? inviteCode) async {
final signalIdentity = await SignalHelper.getSignalIdentity();
final signalIdentity = await getSignalIdentity();
if (signalIdentity == null) {
return Result.error(ErrorCode.InternalError);
}
final signalStore =
await SignalHelper.getSignalStoreFromIdentity(signalIdentity);
final signalStore = await getSignalStoreFromIdentity(signalIdentity);
final signedPreKey = (await signalStore.loadSignedPreKeys())[0];

View file

@ -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/userdata.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/services/notification.service.dart';
// ignore: library_prefixes
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
import 'package:twonly/src/utils/storage.dart';
final lockSendingMessages = Mutex();
@ -138,7 +137,7 @@ Future<(String, RetransmitMessage)?> encryptMessage(
{PushKind? pushKind}) async {
return await lockSendingMessages
.protect<(String, RetransmitMessage)?>(() async {
Uint8List? bytes = await SignalHelper.encryptMessage(msg, userId);
Uint8List? bytes = await signalEncryptMessage(userId, msg);
if (bytes == null) {
Logger("api.dart").shout("Error encryption message!");

View file

@ -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/media_received.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';
// ignore: library_prefixes
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
final lockHandleServerMessage = Mutex();
@ -53,7 +53,7 @@ Future handleServerMessage(server.ServerToClient msg) 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) {
Logger("server_messages")
.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 {
List<PreKeyRecord> localPreKeys = await SignalHelper.getPreKeys();
List<PreKeyRecord> localPreKeys = await signalGetPreKeys();
List<client.Response_PreKey> prekeysList = [];
for (int i = 0; i < localPreKeys.length; i++) {

View 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;
}
}

View 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));
}

View 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;
}
}

View 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;
}

View file

@ -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;
}
}

View file

@ -17,7 +17,7 @@ import 'package:twonly/src/views/components/initialsavatar.dart';
import 'package:twonly/src/model/json/message.dart';
import 'package:twonly/src/services/api/messages.dart';
// 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/views/settings/subscription/subscription_view.dart';

View file

@ -9,7 +9,7 @@ import 'package:flutter/material.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.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';
class ContactVerifyView extends StatefulWidget {

View file

@ -5,10 +5,10 @@ import 'package:logging/logging.dart';
import 'package:twonly/globals.dart';
import 'package:flutter/material.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/model/json/userdata.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/signal.dart';
class RegisterView extends StatefulWidget {
const RegisterView({super.key, required this.callbackOnSuccess});