move files

This commit is contained in:
otsmr 2025-01-24 16:07:39 +01:00
parent 85c0f32f21
commit b06c2e1cc8
13 changed files with 356 additions and 204 deletions

View file

@ -0,0 +1,106 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:fixnum/fixnum.dart';
import 'package:twonly/src/utils/json.dart';
part 'message.g.dart';
enum MessageKind { textMessage, image, video, contactRequest }
// so _$MessageKindEnumMap gets generated
@JsonSerializable()
class _MessageKind {
MessageKind? kind;
}
@JsonSerializable()
class Message {
@Int64Converter()
final Int64 fromUserId;
final MessageKind kind;
final MessageContent? content;
DateTime timestamp;
Message(
{required this.fromUserId,
required this.kind,
this.content,
required this.timestamp});
@override
String toString() {
return 'Message(kind: $kind, content: $content, timestamp: $timestamp)';
}
Message fromJson(Map<String, dynamic> json) {
dynamic content;
MessageKind kind = $enumDecode(_$MessageKindEnumMap, json['kind']);
switch (kind) {
case MessageKind.textMessage:
content = TextContent.fromJson(json["content"]);
break;
case MessageKind.image:
content = ImageContent.fromJson(json["content"]);
break;
default:
}
return Message(
fromUserId: const Int64Converter().fromJson(json['fromUserId'] as String),
kind: kind,
timestamp: DateTime.parse(json['timestamp'] as String),
content: content,
);
}
Map<String, dynamic> toJson(Message instance) {
var json = <String, dynamic>{
'fromUserId': const Int64Converter().toJson(instance.fromUserId),
'kind': _$MessageKindEnumMap[instance.kind]!,
'timestamp': instance.timestamp.toIso8601String(),
'content': instance.content
};
return json;
}
}
abstract class MessageContent {
MessageContent();
factory MessageContent.fromJson(Map<String, dynamic> json) {
return TextContent("");
}
Map<String, dynamic> toJson();
}
// factory MessageContent.fromJson(Map<String, dynamic> json) =>
// _$MessageContentFromJson(json);
@JsonSerializable()
class TextContent extends MessageContent {
final String text;
TextContent(this.text);
factory TextContent.fromJson(Map<String, dynamic> json) =>
_$TextContentFromJson(json);
@override
Map<String, dynamic> toJson() => _$TextContentToJson(this);
}
@JsonSerializable()
class ImageContent extends MessageContent {
final String imageUrl;
ImageContent(this.imageUrl);
factory ImageContent.fromJson(Map<String, dynamic> json) =>
_$ImageContentFromJson(json);
@override
Map<String, dynamic> toJson() => _$ImageContentToJson(this);
}
// @JsonSerializable()
// class VideoContent extends MessageContent {
// final String videoUrl;
// VideoContent(this.videoUrl);
// }

View file

@ -0,0 +1,56 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'message.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_MessageKind _$MessageKindFromJson(Map<String, dynamic> json) => _MessageKind()
..kind = $enumDecodeNullable(_$MessageKindEnumMap, json['kind']);
Map<String, dynamic> _$MessageKindToJson(_MessageKind instance) =>
<String, dynamic>{
'kind': _$MessageKindEnumMap[instance.kind],
};
const _$MessageKindEnumMap = {
MessageKind.textMessage: 'textMessage',
MessageKind.image: 'image',
MessageKind.video: 'video',
MessageKind.contactRequest: 'contactRequest',
};
Message _$MessageFromJson(Map<String, dynamic> json) => Message(
fromUserId: const Int64Converter().fromJson(json['fromUserId'] as String),
kind: $enumDecode(_$MessageKindEnumMap, json['kind']),
content: json['content'] == null
? null
: MessageContent.fromJson(json['content'] as Map<String, dynamic>),
timestamp: DateTime.parse(json['timestamp'] as String),
);
Map<String, dynamic> _$MessageToJson(Message instance) => <String, dynamic>{
'fromUserId': const Int64Converter().toJson(instance.fromUserId),
'kind': _$MessageKindEnumMap[instance.kind]!,
'content': instance.content,
'timestamp': instance.timestamp.toIso8601String(),
};
TextContent _$TextContentFromJson(Map<String, dynamic> json) => TextContent(
json['text'] as String,
);
Map<String, dynamic> _$TextContentToJson(TextContent instance) =>
<String, dynamic>{
'text': instance.text,
};
ImageContent _$ImageContentFromJson(Map<String, dynamic> json) => ImageContent(
json['imageUrl'] as String,
);
Map<String, dynamic> _$ImageContentToJson(ImageContent instance) =>
<String, dynamic>{
'imageUrl': instance.imageUrl,
};

View file

@ -1,22 +1,8 @@
import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:twonly/src/model/user_data_json.dart'; import 'package:twonly/src/utils/json.dart';
part 'signal_identity_json.g.dart'; part 'signal_identity.g.dart';
class Uint8ListConverter implements JsonConverter<Uint8List, String> {
const Uint8ListConverter();
@override
Uint8List fromJson(String json) {
return base64Decode(json);
}
@override
String toJson(Uint8List object) {
return base64Encode(object);
}
}
@JsonSerializable() @JsonSerializable()
class SignalIdentity { class SignalIdentity {

View file

@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'signal_identity_json.dart'; part of 'signal_identity.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator

View file

@ -1,20 +1,7 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
part 'user_data_json.g.dart'; import 'package:twonly/src/utils/json.dart';
part 'user_data.g.dart';
class Int64Converter implements JsonConverter<Int64, String> {
const Int64Converter();
@override
Int64 fromJson(String json) {
return Int64.parseInt(json);
}
@override
String toJson(Int64 object) {
return object.toString();
}
}
@JsonSerializable() @JsonSerializable()
class UserData { class UserData {

View file

@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user_data_json.dart'; part of 'user_data.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator

View file

@ -3,17 +3,18 @@ import 'dart:math';
import 'package:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:twonly/src/proto/api/client_to_server.pb.dart' as client; import 'package:twonly/src/proto/api/client_to_server.pb.dart' as client;
import 'package:twonly/src/proto/api/client_to_server.pbserver.dart'; import 'package:twonly/src/proto/api/client_to_server.pbserver.dart';
import 'package:twonly/src/proto/api/error.pb.dart'; import 'package:twonly/src/proto/api/error.pb.dart';
import 'package:twonly/src/proto/api/server_to_client.pb.dart' as server; import 'package:twonly/src/proto/api/server_to_client.pb.dart' as server;
import 'package:twonly/src/signal/signal_helper.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.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
import 'package:libsignal_protocol_dart/src/ecc/ed25519.dart'; import 'package:libsignal_protocol_dart/src/ecc/ed25519.dart';
import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:web_socket_channel/web_socket_channel.dart';

View file

@ -1,13 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:fixnum/fixnum.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:logging/logging.dart'; import 'package:twonly/src/utils/signal.dart';
import 'package:twonly/src/model/signal_identity_json.dart';
import 'package:twonly/src/proto/api/server_to_client.pb.dart';
import 'package:twonly/src/utils/misc.dart';
import 'connect_sender_key_store.dart'; import 'connect_sender_key_store.dart';
import 'connect_signal_protocol_store.dart'; import 'connect_signal_protocol_store.dart';
@ -23,8 +18,8 @@ class SignalDataModel {
// Session validation // Session validation
Future<Fingerprint?> generateSessionFingerPrint(String target) async { Future<Fingerprint?> generateSessionFingerPrint(String target) async {
try { try {
IdentityKey? targetIdentity = await signalStore.getIdentity( IdentityKey? targetIdentity = await signalStore
SignalProtocolAddress(target, SignalHelper.defaultDeviceId)); .getIdentity(SignalProtocolAddress(target, defaultDeviceId));
if (targetIdentity != null) { if (targetIdentity != null) {
final generator = NumericFingerprintGenerator(5200); final generator = NumericFingerprintGenerator(5200);
final localFingerprint = generator.createFor( final localFingerprint = generator.createFor(
@ -48,8 +43,8 @@ class SignalDataModel {
Future<String?> getEncryptedText(String text, String target) async { Future<String?> getEncryptedText(String text, String target) async {
try { try {
SessionCipher session = SessionCipher.fromStore(signalStore, SessionCipher session = SessionCipher.fromStore(
SignalProtocolAddress(target, SignalHelper.defaultDeviceId)); signalStore, SignalProtocolAddress(target, defaultDeviceId));
final ciphertext = final ciphertext =
await session.encrypt(Uint8List.fromList(utf8.encode(text))); await session.encrypt(Uint8List.fromList(utf8.encode(text)));
Map<String, dynamic> data = { Map<String, dynamic> data = {
@ -65,8 +60,8 @@ class SignalDataModel {
Future<String?> getDecryptedText(String source, String msg) async { Future<String?> getDecryptedText(String source, String msg) async {
try { try {
SessionCipher session = SessionCipher.fromStore(signalStore, SessionCipher session = SessionCipher.fromStore(
SignalProtocolAddress(source, SignalHelper.defaultDeviceId)); signalStore, SignalProtocolAddress(source, defaultDeviceId));
Map data = jsonDecode(msg); Map data = jsonDecode(msg);
if (data["type"] == CiphertextMessage.prekeyType) { if (data["type"] == CiphertextMessage.prekeyType) {
PreKeySignalMessage pre = PreKeySignalMessage pre =
@ -89,156 +84,3 @@ class SignalDataModel {
} }
} }
} }
int userIdToRegistrationId(List<int> userId) {
int result = 0;
for (int i = 8; i < 16; i++) {
result = (result << 8) | userId[i];
}
return result;
}
String uint8ListToHex(List<int> list) {
final StringBuffer hexBuffer = StringBuffer();
for (int byte in list) {
hexBuffer.write(byte.toRadixString(16).padLeft(2, '0'));
}
return hexBuffer.toString().toUpperCase();
}
class SignalHelper {
static const int defaultDeviceId = 1;
static Future<ECPrivateKey?> getPrivateKey() async {
final signalIdentity = await getSignalIdentity();
if (signalIdentity == null) {
return null;
}
final IdentityKeyPair identityKeyPair =
IdentityKeyPair.fromSerialized(signalIdentity.identityKeyPairU8List);
return identityKeyPair.getPrivateKey();
}
static Future<bool> addNewContact(Response_UserData userData) async {
final Int64 userId = userData.userId;
SignalProtocolAddress targetAddress =
SignalProtocolAddress(userId.toString(), SignalHelper.defaultDeviceId);
SignalProtocolStore? signalStore = await SignalHelper.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;
}
}
static Future<ConnectSignalProtocolStore?> getSignalStore() async {
return await getSignalStoreFromIdentity((await getSignalIdentity())!);
}
static Future<SignalIdentity?> getSignalIdentity() async {
final storage = getSecureStorage();
final signalIdentityJson = await storage.read(key: "signal_identity");
if (signalIdentityJson == null) {
return null;
}
return SignalIdentity.fromJson(jsonDecode(signalIdentityJson));
}
static Future<ConnectSignalProtocolStore> getSignalStoreFromIdentity(
SignalIdentity signalIdentity) async {
final IdentityKeyPair identityKeyPair =
IdentityKeyPair.fromSerialized(signalIdentity.identityKeyPairU8List);
return ConnectSignalProtocolStore(
identityKeyPair, signalIdentity.registrationId.toInt());
}
static 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;
}
static 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, SignalHelper.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));
}
}

View file

@ -2,10 +2,11 @@ import 'dart:convert';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:twonly/main.dart'; import 'package:twonly/main.dart';
import 'package:twonly/src/model/contacts_model.dart'; import 'package:twonly/src/model/contacts_model.dart';
import 'package:twonly/src/signal/signal_helper.dart';
import 'package:twonly/src/providers/api_provider.dart'; import 'package:twonly/src/providers/api_provider.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/model/user_data_json.dart'; // ignore: library_prefixes
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
import 'package:twonly/src/model/json/user_data.dart';
Future<bool> addNewContact(String username) async { Future<bool> addNewContact(String username) async {
final res = await apiProvider.getUserData(username); final res = await apiProvider.getUserData(username);

31
lib/src/utils/json.dart Normal file
View file

@ -0,0 +1,31 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:fixnum/fixnum.dart';
import 'dart:convert';
import 'dart:typed_data';
class Int64Converter implements JsonConverter<Int64, String> {
const Int64Converter();
@override
Int64 fromJson(String json) {
return Int64.parseInt(json);
}
@override
String toJson(Int64 object) {
return object.toString();
}
}
class Uint8ListConverter implements JsonConverter<Uint8List, String> {
const Uint8ListConverter();
@override
Uint8List fromJson(String json) {
return base64Decode(json);
}
@override
String toJson(Uint8List object) {
return base64Encode(object);
}
}

142
lib/src/utils/signal.dart Normal file
View file

@ -0,0 +1,142 @@
import 'dart:convert';
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/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 {
final storage = getSecureStorage();
final signalIdentityJson = await storage.read(key: "signal_identity");
if (signalIdentityJson == null) {
return null;
}
return SignalIdentity.fromJson(jsonDecode(signalIdentityJson));
}
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));
}

View file

@ -1,7 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:twonly/main.dart'; import 'package:twonly/main.dart';
import 'package:twonly/src/model/user_data_json.dart'; import 'package:twonly/src/model/json/user_data.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
Future<bool> isUserCreated() async { Future<bool> isUserCreated() async {

View file

@ -1,4 +1,4 @@
import 'package:twonly/src/model/user_data_json.dart'; import 'package:twonly/src/model/json/user_data.dart';
import 'package:restart_app/restart_app.dart'; import 'package:restart_app/restart_app.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/settings/settings_controller.dart'; import 'package:twonly/src/settings/settings_controller.dart';