From 5bc1136ec0bad279930ade2b1025034d09c34028 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sun, 25 Dec 2022 22:59:53 +0100 Subject: [PATCH] feat: Implement getting fingerprints --- lib/src/omemo/events.dart | 6 +-- lib/src/omemo/fingerprint.dart | 1 - lib/src/omemo/omemomanager.dart | 66 +++++++++++++++++++++++++----- lib/src/omemo/ratchet_map_key.dart | 1 - lib/src/omemo/sessionmanager.dart | 8 ++-- test/omemo_test.dart | 2 +- 6 files changed, 64 insertions(+), 20 deletions(-) diff --git a/lib/src/omemo/events.dart b/lib/src/omemo/events.dart index 71b7f9b..cf075ce 100644 --- a/lib/src/omemo/events.dart +++ b/lib/src/omemo/events.dart @@ -22,9 +22,9 @@ class RatchetRemovedEvent extends OmemoEvent { } /// Triggered when the device map has been modified -class DeviceMapModifiedEvent extends OmemoEvent { - DeviceMapModifiedEvent(this.map); - final Map> map; +class DeviceListModifiedEvent extends OmemoEvent { + DeviceListModifiedEvent(this.list); + final Map> list; } /// Triggered by the OmemoSessionManager when our own device bundle was modified diff --git a/lib/src/omemo/fingerprint.dart b/lib/src/omemo/fingerprint.dart index 7c50326..afb72df 100644 --- a/lib/src/omemo/fingerprint.dart +++ b/lib/src/omemo/fingerprint.dart @@ -2,7 +2,6 @@ import 'package:meta/meta.dart'; @immutable class DeviceFingerprint { - const DeviceFingerprint(this.deviceId, this.fingerprint); final String fingerprint; final int deviceId; diff --git a/lib/src/omemo/omemomanager.dart b/lib/src/omemo/omemomanager.dart index 43513cf..ff4f30b 100644 --- a/lib/src/omemo/omemomanager.dart +++ b/lib/src/omemo/omemomanager.dart @@ -3,6 +3,7 @@ import 'dart:collection'; import 'dart:convert'; import 'package:collection/collection.dart'; import 'package:cryptography/cryptography.dart'; +import 'package:hex/hex.dart'; import 'package:logging/logging.dart'; import 'package:meta/meta.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/encryption_result.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/stanza.dart'; import 'package:omemo_dart/src/protobuf/omemo_authenticated_message.dart'; @@ -48,7 +50,6 @@ class OmemoManager { Map> _deviceList = {}; /// Map bare JIDs to whether we already requested the device list once final Map _deviceListRequested = {}; - /// Map bare a ratchet key to its ratchet. Note that this is also locked by /// _ratchetCriticalSectionLock. Map _ratchetMap = {}; @@ -126,14 +127,14 @@ class OmemoManager { _deviceList[jid] = [deviceId]; // Commit the device map - _eventStreamController.add(DeviceMapModifiedEvent(_deviceList)); + _eventStreamController.add(DeviceListModifiedEvent(_deviceList)); } else { // Prevent having the same device multiple times in the list if (!_deviceList[jid]!.contains(deviceId)) { _deviceList[jid]!.add(deviceId); // 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 /// will return null as there is no message to be decrypted. This, however, is used /// to set up sessions or advance the ratchets. - Future decryptMessage(List? ciphertext, String senderJid, int senderDeviceId, List keys, int timestamp) async { + Future _decryptMessage(List? ciphertext, String senderJid, int senderDeviceId, List keys, int timestamp) async { // Try to find a session we can decrypt with. var device = await getDevice(); final rawKey = keys.firstWhereOrNull((key) => key.rid == device.id); @@ -263,7 +264,7 @@ class OmemoManager { // Guard against old key exchanges 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) { throw InvalidKeyExchangeException(); } @@ -368,6 +369,9 @@ class OmemoManager { return !_ratchetMap.containsKey(RatchetMapKey(jid, id)) || _deviceList[jid]?.contains(id) == false; }).toList(); + + // Trigger an event with the new device list + _eventStreamController.add(DeviceListModifiedEvent(_deviceList)); } else { // We already have an up-to-date version of the device list bundlesToFetch = _deviceList[jid]! @@ -502,7 +506,9 @@ class OmemoManager { encryptedKeys, ); } - + + /// Call when receiving an OMEMO:2 encrypted stanza. Will handle everything and + /// decrypt it. Future onIncomingStanza(OmemoIncomingStanza stanza) async { await _enterRatchetCriticalSection(stanza.bareSenderJid); @@ -510,7 +516,7 @@ class OmemoManager { final ratchetCreated = !_ratchetMap.containsKey(ratchetKey); String? payload; try { - payload = await decryptMessage( + payload = await _decryptMessage( base64.decode(stanza.payload), stanza.bareSenderJid, stanza.senderDeviceId, @@ -569,6 +575,8 @@ class OmemoManager { ); } + /// Call when sending out an encrypted stanza. Will handle everything and + /// encrypt it. Future onOutgoingStanza(OmemoOutgoingStanza stanza) async { return _encryptToJids( stanza.recipientJids, @@ -589,20 +597,58 @@ class OmemoManager { 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 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 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?> 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.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. void onNewConnection() { _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 devices) { _deviceList[jid] = devices; _deviceListRequested[jid] = true; - } - List? getDeviceListForJid(String jid) => _deviceList[jid]; + // Trigger an event + _eventStreamController.add(DeviceListModifiedEvent(_deviceList)); + } void initialize(Map ratchetMap, Map> deviceList) { _deviceList = deviceList; diff --git a/lib/src/omemo/ratchet_map_key.dart b/lib/src/omemo/ratchet_map_key.dart index 79ed90c..76b0681 100644 --- a/lib/src/omemo/ratchet_map_key.dart +++ b/lib/src/omemo/ratchet_map_key.dart @@ -2,7 +2,6 @@ import 'package:meta/meta.dart'; @immutable class RatchetMapKey { - const RatchetMapKey(this.jid, this.deviceId); factory RatchetMapKey.fromJsonKey(String key) { diff --git a/lib/src/omemo/sessionmanager.dart b/lib/src/omemo/sessionmanager.dart index 77a06f5..e731d7f 100644 --- a/lib/src/omemo/sessionmanager.dart +++ b/lib/src/omemo/sessionmanager.dart @@ -119,14 +119,14 @@ class OmemoSessionManager { _deviceMap[jid] = [deviceId]; // Commit the device map - _eventStreamController.add(DeviceMapModifiedEvent(_deviceMap)); + _eventStreamController.add(DeviceListModifiedEvent(_deviceMap)); } else { // Prevent having the same device multiple times in the list if (!_deviceMap[jid]!.contains(deviceId)) { _deviceMap[jid]!.add(deviceId); // Commit the device map - _eventStreamController.add(DeviceMapModifiedEvent(_deviceMap)); + _eventStreamController.add(DeviceListModifiedEvent(_deviceMap)); } } @@ -562,7 +562,7 @@ class OmemoSessionManager { _deviceMap.remove(jid); } // Commit it - _eventStreamController.add(DeviceMapModifiedEvent(_deviceMap)); + _eventStreamController.add(DeviceListModifiedEvent(_deviceMap)); }); } @@ -580,7 +580,7 @@ class OmemoSessionManager { // Remove the device from jid _deviceMap.remove(jid); // Commit it - _eventStreamController.add(DeviceMapModifiedEvent(_deviceMap)); + _eventStreamController.add(DeviceListModifiedEvent(_deviceMap)); }); } diff --git a/test/omemo_test.dart b/test/omemo_test.dart index bc2b400..c31679e 100644 --- a/test/omemo_test.dart +++ b/test/omemo_test.dart @@ -65,7 +65,7 @@ void main() { deviceModified = true; } else if (event is RatchetModifiedEvent) { ratchetModified++; - } else if (event is DeviceMapModifiedEvent) { + } else if (event is DeviceListModifiedEvent) { deviceMapModified++; } });