twonly-app/lib/src/utils/signal.dart
2025-01-26 01:25:59 +01:00

265 lines
8 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:fixnum/fixnum.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/proto/api/server_to_client.pb.dart';
import 'package:twonly/src/signal/connect_signal_protocol_store.dart';
import 'package:twonly/src/utils/misc.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 Int64 userId = userData.userId;
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();
}
// Signed pre key calculation
int tempSignedPreKeyId = userData.signedPrekeyId.toInt();
// Map? tempSignedPreKey = remoteBundle["signedPreKey"];
ECPublicKey? tempSignedPreKeyPublic;
Uint8List? tempSignedPreKeySignature;
// if (tempSignedPreKey != null) {
tempSignedPreKeyPublic = Curve.decodePoint(
DjbECPublicKey(Uint8List.fromList(userData.signedPrekey)).serialize(), 1);
tempSignedPreKeySignature =
Uint8List.fromList(userData.signedPrekeySignature);
// }
// Identity key calculation
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 = getSecureStorage();
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 = getSecureStorage();
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: Int64(registrationId));
await storage.write(
key: "signal_identity", value: jsonEncode(storedSignalIdentity));
}
// Future<Fingerprint?> generateSessionFingerPrint(String target) async {
// try {
// IdentityKey? targetIdentity = await signalStore
// .getIdentity(SignalProtocolAddress(target, defaultDeviceId));
// if (targetIdentity != null) {
// final generator = NumericFingerprintGenerator(5200);
// final localFingerprint = generator.createFor(
// 1,
// userId,
// (await signalStore.getIdentityKeyPair()).getPublicKey(),
// Uint8List.fromList(utf8.encode(target)),
// targetIdentity,
// );
// return localFingerprint;
// }
// return null;
// } catch (e) {
// return null;
// }
// }
Uint8List intToBytes(int value) {
final byteData = ByteData(4);
byteData.setInt32(0, value, Endian.big);
return byteData.buffer.asUint8List();
}
int bytesToInt(Uint8List bytes) {
final byteData = ByteData.sublistView(bytes);
return byteData.getInt32(0, Endian.big);
}
List<Uint8List>? removeLastFourBytes(Uint8List original) {
if (original.length < 4) {
return null;
}
final newList = Uint8List(original.length - 4);
newList.setAll(0, original.sublist(0, original.length - 4));
final lastFourBytes = original.sublist(original.length - 4);
return [newList, lastFourBytes];
}
Future<Uint8List?> encryptBytes(Uint8List bytes, Int64 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(bytes)));
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<Uint8List?> encryptMessage(Message msg, Int64 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(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<Message?> getDecryptedText(Int64 source, Uint8List msg) async {
try {
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
SessionCipher session = SessionCipher.fromStore(
signalStore, SignalProtocolAddress(source.toString(), defaultDeviceId));
List<Uint8List>? msgs = removeLastFourBytes(msg);
if (msgs == null) return null;
Uint8List body = msgs[0];
int type = bytesToInt(msgs[1]);
// gzip.decode(body);
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 {
return null;
}
Message dectext = Message.fromJson(utf8.decode(gzip.decode(plaintext)));
return dectext;
} catch (e) {
Logger("utils/signal").shout(e.toString());
return null;
}
}