From dad85b8467924634358d9be113b0e20af618b998 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sat, 17 Jun 2023 16:15:09 +0200 Subject: [PATCH] feat: Implement deferred loading of ratchet data --- lib/src/omemo/omemo.dart | 58 +++++++++++++++++++++++++++++++++++++++ lib/src/trust/always.dart | 3 ++ lib/src/trust/base.dart | 9 ++++++ lib/src/trust/btbv.dart | 39 ++++++++++++++++++-------- lib/src/trust/never.dart | 3 ++ 5 files changed, 101 insertions(+), 11 deletions(-) diff --git a/lib/src/omemo/omemo.dart b/lib/src/omemo/omemo.dart index 9359a5e..fb73572 100644 --- a/lib/src/omemo/omemo.dart +++ b/lib/src/omemo/omemo.dart @@ -27,6 +27,16 @@ import 'package:omemo_dart/src/trust/base.dart'; import 'package:omemo_dart/src/x3dh/x3dh.dart'; import 'package:synchronized/synchronized.dart'; +class OmemoDataPackage { + const OmemoDataPackage(this.devices, this.ratchets); + + /// The device list for the given JID. + final List devices; + + /// The ratchets for the JID. + final Map ratchets; +} + /// Callback type definitions /// Directly "package" [result] into an OMEMO message and send it to [recipientJid]. @@ -84,6 +94,12 @@ typedef RemoveRatchetsFunction = Future Function( /// A stub implementation of [RemoveRatchetsFunction]. Future removeRatchetsStub(List ratchets) async {} +/// Loads all the required data for the ratchets of [jid]. +typedef LoadRatchetsCallback = Future Function(String jid); + +/// A stub implementation of [LoadRatchetsCallback]. +Future loadRatchetsStub(String _) async => null; + class OmemoManager { OmemoManager( this._device, @@ -96,6 +112,7 @@ class OmemoManager { this.commitDeviceList = commitDeviceListStub, this.commitDevice = commitDeviceStub, this.removeRatchets = removeRatchetsStub, + this.loadRatchets = loadRatchetsStub, }); final Logger _log = Logger('OmemoManager'); @@ -128,6 +145,9 @@ class OmemoManager { /// Callback to remove ratchets from persistent storage. final RemoveRatchetsFunction removeRatchets; + /// Callback to load ratchets from persistent storage. + final LoadRatchetsCallback loadRatchets; + /// Map bare JID to its known devices final Map> _deviceList = {}; @@ -141,6 +161,9 @@ class OmemoManager { /// Map bare JID to whether we already tried to subscribe to the device list node. final Map _subscriptionMap = {}; + /// List of JIDs for which we cached trust data, the device list, and the ratchets. + final List _cachedJids = []; + /// For preventing a race condition in encryption/decryption final RatchetAccessQueue _ratchetQueue = RatchetAccessQueue(); @@ -153,6 +176,33 @@ class OmemoManager { // ignore: prefer_final_fields OmemoDevice _device; + Future _cacheJidsIfNeccessary(List jids) async { + for (final jid in jids) { + await _cacheJidIfNeccessary(jid); + } + } + + Future _cacheJidIfNeccessary(String jid) async { + // JID is already cached. We don't have to do anything. + if (_cachedJids.contains(jid)) { + return; + } + + _cachedJids.add(jid); + final result = await loadRatchets(jid); + if (result == null) { + _log.fine('Did not load ratchet data for $jid. Assuming there is none.'); + return; + } + + // Cache the data + _deviceList[jid] = result.devices; + _ratchetMap.addAll(result.ratchets); + + // Load trust data + await trustManager.loadTrustData(jid); + } + Future> _decryptAndVerifyHmac( List? ciphertext, List keyAndHmac, @@ -294,6 +344,9 @@ class OmemoManager { Future _onIncomingStanzaImpl( OmemoIncomingStanza stanza, ) async { + // Populate the cache + await _cacheJidIfNeccessary(stanza.bareSenderJid); + // Find the correct key for our device final deviceId = await getDeviceId(); final key = stanza.keys.firstWhereOrNull((key) => key.rid == deviceId); @@ -530,6 +583,9 @@ class OmemoManager { Future _onOutgoingStanzaImpl( OmemoOutgoingStanza stanza, ) async { + // Populate the cache + await _cacheJidsIfNeccessary(stanza.recipientJids); + // Encrypt the payload, if we have any final List payloadKey; final List ciphertext; @@ -761,6 +817,8 @@ class OmemoManager { } await removeRatchets(keys.toList()); + // TODO: Do we have to tell the trust manager? + // Clear the device list await commitDeviceList( jid, diff --git a/lib/src/trust/always.dart b/lib/src/trust/always.dart index eb6a47b..d35a0de 100644 --- a/lib/src/trust/always.dart +++ b/lib/src/trust/always.dart @@ -20,4 +20,7 @@ class AlwaysTrustingTrustManager extends TrustManager { @override Future removeTrustDecisionsForJid(String jid) async {} + + @override + Future loadTrustData(String jid) async {} } diff --git a/lib/src/trust/base.dart b/lib/src/trust/base.dart index 0d2547f..cd0e72f 100644 --- a/lib/src/trust/base.dart +++ b/lib/src/trust/base.dart @@ -1,3 +1,5 @@ +import 'package:meta/meta.dart'; + /// The base class for managing trust in OMEMO sessions. // ignore: one_member_abstracts abstract class TrustManager { @@ -19,4 +21,11 @@ abstract class TrustManager { /// Removes all trust decisions for [jid]. Future removeTrustDecisionsForJid(String jid); + + // ignore: comment_references + /// Called from within the [OmemoManager]. + /// Loads the trust data for the JID [jid] from persistent storage + /// into the internal cache, if applicable. + @internal + Future loadTrustData(String jid); } diff --git a/lib/src/trust/btbv.dart b/lib/src/trust/btbv.dart index 7640882..496fbd2 100644 --- a/lib/src/trust/btbv.dart +++ b/lib/src/trust/btbv.dart @@ -1,4 +1,5 @@ import 'package:meta/meta.dart'; +import 'package:omemo_dart/src/helpers.dart'; import 'package:omemo_dart/src/omemo/ratchet_map_key.dart'; import 'package:omemo_dart/src/trust/base.dart'; import 'package:synchronized/synchronized.dart'; @@ -37,6 +38,12 @@ typedef BTBVRemoveTrustForJidCallback = Future Function(String jid); /// A stub-implementation of [BTBVRemoveTrustForJidCallback]. Future btbvRemoveTrustStub(String _) async {} +/// A callback for when trust data should be loaded. +typedef BTBVLoadDataCallback = Future> Function(String jid); + +/// A stub-implementation for [BTBVLoadDataCallback]. +Future> btbvLoadDataStub(String _) async => []; + /// Every device is in either of those two trust states: /// - notTrusted: The device is absolutely not trusted /// - blindTrust: The fingerprint is not verified using OOB means @@ -56,33 +63,31 @@ enum BTBVTrustState { /// See https://gultsch.de/trust.html for more details. class BlindTrustBeforeVerificationTrustManager extends TrustManager { BlindTrustBeforeVerificationTrustManager({ - Map? trustCache, - Map? enablementCache, - Map>? devices, + this.loadData = btbvLoadDataStub, this.commit = btbvCommitStub, this.removeTrust = btbvRemoveTrustStub, - }) : trustCache = trustCache ?? {}, - enablementCache = enablementCache ?? {}, - devices = devices ?? {}, - _lock = Lock(); + }); /// The cache for mapping a RatchetMapKey to its trust state @visibleForTesting @protected - final Map trustCache; + final Map trustCache = {}; /// The cache for mapping a RatchetMapKey to whether it is enabled or not @visibleForTesting @protected - final Map enablementCache; + final Map enablementCache = {}; /// Mapping of Jids to their device identifiers @visibleForTesting @protected - final Map> devices; + final Map> devices = {}; /// The lock for devices and trustCache - final Lock _lock; + final Lock _lock = Lock(); + + /// Callback for loading trust data. + final BTBVLoadDataCallback loadData; /// Callback for commiting trust data to persistent storage. final BTBVTrustCommitCallback commit; @@ -241,6 +246,18 @@ class BlindTrustBeforeVerificationTrustManager extends TrustManager { }); } + @override + Future loadTrustData(String jid) async { + await _lock.synchronized(() async { + for (final result in await loadData(jid)) { + final key = RatchetMapKey(jid, result.device); + trustCache[key] = result.state; + enablementCache[key] = result.enabled; + devices.appendOrCreate(jid, result.device); + } + }); + } + @visibleForTesting BTBVTrustState getDeviceTrust(String jid, int deviceId) => trustCache[RatchetMapKey(jid, deviceId)]!; diff --git a/lib/src/trust/never.dart b/lib/src/trust/never.dart index 204ae74..0afba81 100644 --- a/lib/src/trust/never.dart +++ b/lib/src/trust/never.dart @@ -20,4 +20,7 @@ class NeverTrustingTrustManager extends TrustManager { @override Future removeTrustDecisionsForJid(String jid) async {} + + @override + Future loadTrustData(String jid) async {} }