feat: Implement getting fingerprints

This commit is contained in:
PapaTutuWawa 2022-12-25 22:59:53 +01:00
parent d37a4bd719
commit 5bc1136ec0
6 changed files with 64 additions and 20 deletions

View File

@ -22,9 +22,9 @@ class RatchetRemovedEvent extends OmemoEvent {
} }
/// Triggered when the device map has been modified /// Triggered when the device map has been modified
class DeviceMapModifiedEvent extends OmemoEvent { class DeviceListModifiedEvent extends OmemoEvent {
DeviceMapModifiedEvent(this.map); DeviceListModifiedEvent(this.list);
final Map<String, List<int>> map; final Map<String, List<int>> list;
} }
/// Triggered by the OmemoSessionManager when our own device bundle was modified /// Triggered by the OmemoSessionManager when our own device bundle was modified

View File

@ -2,7 +2,6 @@ import 'package:meta/meta.dart';
@immutable @immutable
class DeviceFingerprint { class DeviceFingerprint {
const DeviceFingerprint(this.deviceId, this.fingerprint); const DeviceFingerprint(this.deviceId, this.fingerprint);
final String fingerprint; final String fingerprint;
final int deviceId; final int deviceId;

View File

@ -3,6 +3,7 @@ import 'dart:collection';
import 'dart:convert'; import 'dart:convert';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:cryptography/cryptography.dart'; import 'package:cryptography/cryptography.dart';
import 'package:hex/hex.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:omemo_dart/src/crypto.dart'; import 'package:omemo_dart/src/crypto.dart';
@ -16,6 +17,7 @@ import 'package:omemo_dart/src/omemo/device.dart';
import 'package:omemo_dart/src/omemo/encrypted_key.dart'; import 'package:omemo_dart/src/omemo/encrypted_key.dart';
import 'package:omemo_dart/src/omemo/encryption_result.dart'; import 'package:omemo_dart/src/omemo/encryption_result.dart';
import 'package:omemo_dart/src/omemo/events.dart'; import 'package:omemo_dart/src/omemo/events.dart';
import 'package:omemo_dart/src/omemo/fingerprint.dart';
import 'package:omemo_dart/src/omemo/ratchet_map_key.dart'; import 'package:omemo_dart/src/omemo/ratchet_map_key.dart';
import 'package:omemo_dart/src/omemo/stanza.dart'; import 'package:omemo_dart/src/omemo/stanza.dart';
import 'package:omemo_dart/src/protobuf/omemo_authenticated_message.dart'; import 'package:omemo_dart/src/protobuf/omemo_authenticated_message.dart';
@ -48,7 +50,6 @@ class OmemoManager {
Map<String, List<int>> _deviceList = {}; Map<String, List<int>> _deviceList = {};
/// Map bare JIDs to whether we already requested the device list once /// Map bare JIDs to whether we already requested the device list once
final Map<String, bool> _deviceListRequested = {}; final Map<String, bool> _deviceListRequested = {};
/// Map bare a ratchet key to its ratchet. Note that this is also locked by /// Map bare a ratchet key to its ratchet. Note that this is also locked by
/// _ratchetCriticalSectionLock. /// _ratchetCriticalSectionLock.
Map<RatchetMapKey, OmemoDoubleRatchet> _ratchetMap = {}; Map<RatchetMapKey, OmemoDoubleRatchet> _ratchetMap = {};
@ -126,14 +127,14 @@ class OmemoManager {
_deviceList[jid] = [deviceId]; _deviceList[jid] = [deviceId];
// Commit the device map // Commit the device map
_eventStreamController.add(DeviceMapModifiedEvent(_deviceList)); _eventStreamController.add(DeviceListModifiedEvent(_deviceList));
} else { } else {
// Prevent having the same device multiple times in the list // Prevent having the same device multiple times in the list
if (!_deviceList[jid]!.contains(deviceId)) { if (!_deviceList[jid]!.contains(deviceId)) {
_deviceList[jid]!.add(deviceId); _deviceList[jid]!.add(deviceId);
// Commit the device map // Commit the device map
_eventStreamController.add(DeviceMapModifiedEvent(_deviceList)); _eventStreamController.add(DeviceListModifiedEvent(_deviceList));
} }
} }
@ -239,7 +240,7 @@ class OmemoManager {
/// element, then [ciphertext] must be set to null. In this case, this function /// element, then [ciphertext] must be set to null. In this case, this function
/// will return null as there is no message to be decrypted. This, however, is used /// will return null as there is no message to be decrypted. This, however, is used
/// to set up sessions or advance the ratchets. /// to set up sessions or advance the ratchets.
Future<String?> decryptMessage(List<int>? ciphertext, String senderJid, int senderDeviceId, List<EncryptedKey> keys, int timestamp) async { Future<String?> _decryptMessage(List<int>? ciphertext, String senderJid, int senderDeviceId, List<EncryptedKey> keys, int timestamp) async {
// Try to find a session we can decrypt with. // Try to find a session we can decrypt with.
var device = await getDevice(); var device = await getDevice();
final rawKey = keys.firstWhereOrNull((key) => key.rid == device.id); final rawKey = keys.firstWhereOrNull((key) => key.rid == device.id);
@ -263,7 +264,7 @@ class OmemoManager {
// Guard against old key exchanges // Guard against old key exchanges
if (oldRatchet != null) { if (oldRatchet != null) {
_log.finest('KEX for existent ratchet. ${oldRatchet.pn}'); _log.finest('KEX for existent ratchet ${ratchetKey.toJsonKey()}. ${oldRatchet.kexTimestamp} > $timestamp: ${oldRatchet.kexTimestamp > timestamp}');
if (oldRatchet.kexTimestamp > timestamp) { if (oldRatchet.kexTimestamp > timestamp) {
throw InvalidKeyExchangeException(); throw InvalidKeyExchangeException();
} }
@ -368,6 +369,9 @@ class OmemoManager {
return !_ratchetMap.containsKey(RatchetMapKey(jid, id)) || return !_ratchetMap.containsKey(RatchetMapKey(jid, id)) ||
_deviceList[jid]?.contains(id) == false; _deviceList[jid]?.contains(id) == false;
}).toList(); }).toList();
// Trigger an event with the new device list
_eventStreamController.add(DeviceListModifiedEvent(_deviceList));
} else { } else {
// We already have an up-to-date version of the device list // We already have an up-to-date version of the device list
bundlesToFetch = _deviceList[jid]! bundlesToFetch = _deviceList[jid]!
@ -502,7 +506,9 @@ class OmemoManager {
encryptedKeys, encryptedKeys,
); );
} }
/// Call when receiving an OMEMO:2 encrypted stanza. Will handle everything and
/// decrypt it.
Future<DecryptionResult> onIncomingStanza(OmemoIncomingStanza stanza) async { Future<DecryptionResult> onIncomingStanza(OmemoIncomingStanza stanza) async {
await _enterRatchetCriticalSection(stanza.bareSenderJid); await _enterRatchetCriticalSection(stanza.bareSenderJid);
@ -510,7 +516,7 @@ class OmemoManager {
final ratchetCreated = !_ratchetMap.containsKey(ratchetKey); final ratchetCreated = !_ratchetMap.containsKey(ratchetKey);
String? payload; String? payload;
try { try {
payload = await decryptMessage( payload = await _decryptMessage(
base64.decode(stanza.payload), base64.decode(stanza.payload),
stanza.bareSenderJid, stanza.bareSenderJid,
stanza.senderDeviceId, stanza.senderDeviceId,
@ -569,6 +575,8 @@ class OmemoManager {
); );
} }
/// Call when sending out an encrypted stanza. Will handle everything and
/// encrypt it.
Future<EncryptionResult?> onOutgoingStanza(OmemoOutgoingStanza stanza) async { Future<EncryptionResult?> onOutgoingStanza(OmemoOutgoingStanza stanza) async {
return _encryptToJids( return _encryptToJids(
stanza.recipientJids, stanza.recipientJids,
@ -589,20 +597,58 @@ class OmemoManager {
if (enterCriticalSection) await _leaveRatchetCriticalSection(jid); if (enterCriticalSection) await _leaveRatchetCriticalSection(jid);
} }
/// Generates an entirely new device. May be useful when the user wants to reset their cryptographic
/// identity. Triggers an event to commit it to storage.
Future<void> regenerateDevice({ int opkAmount = 100 }) async {
await _deviceLock.synchronized(() async {
_device = await Device.generateNewDevice(_device.jid, opkAmount: opkAmount);
// Commit it
_eventStreamController.add(DeviceModifiedEvent(_device));
});
}
/// Returns the device used for encryption and decryption.
Future<Device> getDevice() => _deviceLock.synchronized(() => _device); Future<Device> getDevice() => _deviceLock.synchronized(() => _device);
/// Returns the fingerprints for all devices of [jid] that we have a session with.
/// If there are not sessions with [jid], then returns null.
Future<List<DeviceFingerprint>?> getFingerprintsForJid(String jid) async {
if (!_deviceList.containsKey(jid)) return null;
await _enterRatchetCriticalSection(jid);
final fingerprintKeys = _deviceList[jid]!
.map((id) => RatchetMapKey(jid, id))
.where((key) => _ratchetMap.containsKey(key));
final fingerprints = List<DeviceFingerprint>.empty(growable: true);
for (final key in fingerprintKeys) {
fingerprints.add(
DeviceFingerprint(
key.deviceId,
HEX.encode(await _ratchetMap[key]!.ik.getBytes()),
),
);
}
await _leaveRatchetCriticalSection(jid);
return fingerprints;
}
/// Ensures that the device list is fetched again on the next message sending. /// Ensures that the device list is fetched again on the next message sending.
void onNewConnection() { void onNewConnection() {
_deviceListRequested.clear(); _deviceListRequested.clear();
} }
/// Sets the device list for [jid] to [devices]. /// Sets the device list for [jid] to [devices]. Triggers a DeviceListModifiedEvent.
void onDeviceListUpdate(String jid, List<int> devices) { void onDeviceListUpdate(String jid, List<int> devices) {
_deviceList[jid] = devices; _deviceList[jid] = devices;
_deviceListRequested[jid] = true; _deviceListRequested[jid] = true;
}
List<int>? getDeviceListForJid(String jid) => _deviceList[jid]; // Trigger an event
_eventStreamController.add(DeviceListModifiedEvent(_deviceList));
}
void initialize(Map<RatchetMapKey, OmemoDoubleRatchet> ratchetMap, Map<String, List<int>> deviceList) { void initialize(Map<RatchetMapKey, OmemoDoubleRatchet> ratchetMap, Map<String, List<int>> deviceList) {
_deviceList = deviceList; _deviceList = deviceList;

View File

@ -2,7 +2,6 @@ import 'package:meta/meta.dart';
@immutable @immutable
class RatchetMapKey { class RatchetMapKey {
const RatchetMapKey(this.jid, this.deviceId); const RatchetMapKey(this.jid, this.deviceId);
factory RatchetMapKey.fromJsonKey(String key) { factory RatchetMapKey.fromJsonKey(String key) {

View File

@ -119,14 +119,14 @@ class OmemoSessionManager {
_deviceMap[jid] = [deviceId]; _deviceMap[jid] = [deviceId];
// Commit the device map // Commit the device map
_eventStreamController.add(DeviceMapModifiedEvent(_deviceMap)); _eventStreamController.add(DeviceListModifiedEvent(_deviceMap));
} else { } else {
// Prevent having the same device multiple times in the list // Prevent having the same device multiple times in the list
if (!_deviceMap[jid]!.contains(deviceId)) { if (!_deviceMap[jid]!.contains(deviceId)) {
_deviceMap[jid]!.add(deviceId); _deviceMap[jid]!.add(deviceId);
// Commit the device map // Commit the device map
_eventStreamController.add(DeviceMapModifiedEvent(_deviceMap)); _eventStreamController.add(DeviceListModifiedEvent(_deviceMap));
} }
} }
@ -562,7 +562,7 @@ class OmemoSessionManager {
_deviceMap.remove(jid); _deviceMap.remove(jid);
} }
// Commit it // Commit it
_eventStreamController.add(DeviceMapModifiedEvent(_deviceMap)); _eventStreamController.add(DeviceListModifiedEvent(_deviceMap));
}); });
} }
@ -580,7 +580,7 @@ class OmemoSessionManager {
// Remove the device from jid // Remove the device from jid
_deviceMap.remove(jid); _deviceMap.remove(jid);
// Commit it // Commit it
_eventStreamController.add(DeviceMapModifiedEvent(_deviceMap)); _eventStreamController.add(DeviceListModifiedEvent(_deviceMap));
}); });
} }

View File

@ -65,7 +65,7 @@ void main() {
deviceModified = true; deviceModified = true;
} else if (event is RatchetModifiedEvent) { } else if (event is RatchetModifiedEvent) {
ratchetModified++; ratchetModified++;
} else if (event is DeviceMapModifiedEvent) { } else if (event is DeviceListModifiedEvent) {
deviceMapModified++; deviceMapModified++;
} }
}); });