mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-10-09 02:54:14 +00:00
152 lines
4.4 KiB
Dart
152 lines
4.4 KiB
Dart
import 'dart:async';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:collection/collection.dart';
|
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
|
import 'package:twonly/locator.dart';
|
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
|
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
|
as pb;
|
|
import 'package:twonly/src/providers/routing.provider.dart';
|
|
import 'package:twonly/src/utils/log.dart';
|
|
import 'package:twonly/src/utils/misc.dart';
|
|
import 'package:twonly/src/visual/components/verification_success_dialog.comp.dart';
|
|
|
|
class KeyVerificationService {
|
|
static final StreamController<void> _verificationSuccessCloseController =
|
|
StreamController<void>.broadcast();
|
|
|
|
static Stream<void> get onVerificationSuccessClose =>
|
|
_verificationSuccessCloseController.stream;
|
|
|
|
static void notifyVerificationSuccessClose() {
|
|
_verificationSuccessCloseController.add(null);
|
|
}
|
|
|
|
static void closeVerificationFlows(BuildContext context) {
|
|
Navigator.popUntil(context, (route) {
|
|
final name = route.settings.name;
|
|
if (name == null) return false;
|
|
return !name.contains('qr_scanner') && !name.contains('verifybadge');
|
|
});
|
|
}
|
|
|
|
static Future<List<int>> getNewSecretVerificationToken() async {
|
|
final token = getRandomUint8List(16);
|
|
await twonlyDB.keyVerificationDao.insertVerificationToken(token);
|
|
return token.toList();
|
|
}
|
|
|
|
static Future<void> handleScannedVerificationToken(
|
|
int contactId,
|
|
Uint8List contactPubKey,
|
|
List<int> secretToken,
|
|
) async {
|
|
Log.info('Notifying verified user using the secret token');
|
|
|
|
final calculatedMac = await _createVerificationBytes(
|
|
contactId,
|
|
contactPubKey,
|
|
secretToken,
|
|
false,
|
|
);
|
|
|
|
await RustApi.sendEncryptedContent(
|
|
contactId: contactId,
|
|
content: pb.EncryptedContent(
|
|
keyVerificationProof: pb.EncryptedContent_KeyVerificationProof(
|
|
calculatedMac: calculatedMac,
|
|
),
|
|
).writeToBuffer(),
|
|
);
|
|
}
|
|
|
|
static Future<void> handleVerificationSucceeded(int fromUserId) async {
|
|
final contact = await twonlyDB.contactsDao.getContactById(fromUserId);
|
|
final context = rootNavigatorKey.currentContext;
|
|
if (context != null && context.mounted && contact != null) {
|
|
unawaited(
|
|
VerificationSuccessDialog.show(
|
|
context,
|
|
contact,
|
|
message: context.lang.secretQrTokenVerifiedSnackbar(
|
|
getContactDisplayName(contact),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
static Future<void> verifySharedContact({
|
|
required int contactId,
|
|
required List<int> sharedPublicIdentityKey,
|
|
required int senderId,
|
|
}) async {
|
|
if (sharedPublicIdentityKey.isEmpty) {
|
|
Log.info('Shared contact $contactId carries no public key');
|
|
return;
|
|
}
|
|
final publicIdentityKey = await RustSignal.getContactPublicKey(
|
|
contactId: contactId,
|
|
);
|
|
if (publicIdentityKey == null) {
|
|
Log.info('No public key stored for contact $contactId');
|
|
return;
|
|
}
|
|
|
|
if (publicIdentityKey.equals(sharedPublicIdentityKey)) {
|
|
Log.info('Verified a user which was shared by a contact');
|
|
await twonlyDB.keyVerificationDao.addKeyVerification(
|
|
contactId,
|
|
VerificationType.contactSharedByVerified,
|
|
verifiedBy: senderId,
|
|
);
|
|
} else {
|
|
Log.error('Public identity keys do not match for contact $contactId');
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<List<int>> _createVerificationBytes(
|
|
int contactId,
|
|
Uint8List contactPubKey,
|
|
List<int> secretToken,
|
|
bool verifying,
|
|
) async {
|
|
final bytes = <int>[];
|
|
|
|
final userPublicKey = await RustSignal.getUserPublicKey();
|
|
|
|
final ownBytes = [
|
|
..._userIdToLeBytes(userService.currentUser.userId),
|
|
...userPublicKey,
|
|
];
|
|
|
|
final contactBytes = [..._userIdToLeBytes(contactId), ...contactPubKey];
|
|
|
|
if (verifying) {
|
|
bytes
|
|
..addAll(ownBytes)
|
|
..addAll(contactBytes);
|
|
} else {
|
|
bytes
|
|
..addAll(contactBytes)
|
|
..addAll(ownBytes);
|
|
}
|
|
|
|
final hmac = Hmac.sha256();
|
|
final mac = await hmac.calculateMac(
|
|
Uint8List.fromList(bytes),
|
|
secretKey: SecretKey(secretToken),
|
|
);
|
|
|
|
return mac.bytes;
|
|
}
|
|
|
|
List<int> _userIdToLeBytes(int userId) {
|
|
final byteData = ByteData(8)..setInt64(0, userId, Endian.little);
|
|
return byteData.buffer.asUint8List();
|
|
}
|