diff --git a/lib/encryption/encryption.dart b/lib/encryption/encryption.dart index 97f4188..9775fb1 100644 --- a/lib/encryption/encryption.dart +++ b/lib/encryption/encryption.dart @@ -96,6 +96,10 @@ class Encryption { // some ssss thing. We can do this in the background unawaited(Zone.root.run(() => ssss.handleToDeviceEvent(event))); } + if (event.sender == client.userID) { + // maybe we need to re-try SSSS secrets + unawaited(Zone.root.run(() => ssss.periodicallyRequestMissingCache())); + } } Future handleEventUpdate(EventUpdate update) async { @@ -111,6 +115,11 @@ class Encryption { unawaited(Zone.root .run(() => keyVerificationManager.handleEventUpdate(update))); } + if (update.content['sender'] == client.userID && + !update.content['unsigned'].containsKey('transaction_id')) { + // maybe we need to re-try SSSS secrets + unawaited(Zone.root.run(() => ssss.periodicallyRequestMissingCache())); + } } Future decryptToDeviceEvent(ToDeviceEvent event) async { diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index d032704..edf908e 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -451,10 +451,15 @@ class KeyManager { try { await loadSingleKey(room.id, sessionId); } catch (err, stacktrace) { - Logs.error( - '[KeyManager] Failed to access online key backup: ' + - err.toString(), - stacktrace); + if (err is MatrixException && err.errcode == 'M_NOT_FOUND') { + Logs.info( + '[KeyManager] Key not in online key backup, requesting it from other devices...'); + } else { + Logs.error( + '[KeyManager] Failed to access online key backup: ' + + err.toString(), + stacktrace); + } } if (!hadPreviously && getInboundGroupSession(room.id, sessionId, senderKey) != null) { diff --git a/lib/encryption/ssss.dart b/lib/encryption/ssss.dart index f6be44d..4670dbb 100644 --- a/lib/encryption/ssss.dart +++ b/lib/encryption/ssss.dart @@ -258,18 +258,27 @@ class SSSS { } } - Future maybeRequestAll(List devices) async { + Future maybeRequestAll([List devices]) async { for (final type in CACHE_TYPES) { - final secret = await getCached(type); - if (secret == null) { - await request(type, devices); + if (keyIdsFromType(type) != null) { + final secret = await getCached(type); + if (secret == null) { + await request(type, devices); + } } } } - Future request(String type, List devices) async { + Future request(String type, [List devices]) async { // only send to own, verified devices Logs.info('[SSSS] Requesting type ${type}...'); + if (devices == null || devices.isEmpty) { + if (!client.userDeviceKeys.containsKey(client.userID)) { + Logs.warning('[SSSS] User does not have any devices'); + return; + } + devices = client.userDeviceKeys[client.userID].deviceKeys.values.toList(); + } devices.removeWhere((DeviceKeys d) => d.userId != client.userID || !d.verified || @@ -294,6 +303,27 @@ class SSSS { }); } + DateTime _lastCacheRequest; + bool _isPeriodicallyRequestingMissingCache = false; + Future periodicallyRequestMissingCache() async { + if (_isPeriodicallyRequestingMissingCache || + (_lastCacheRequest != null && + DateTime.now() + .subtract(Duration(minutes: 15)) + .isBefore(_lastCacheRequest)) || + client.isUnknownSession) { + // we are already requesting right now or we attempted to within the last 15 min + return; + } + _lastCacheRequest = DateTime.now(); + _isPeriodicallyRequestingMissingCache = true; + try { + await maybeRequestAll(); + } finally { + _isPeriodicallyRequestingMissingCache = false; + } + } + Future handleToDeviceEvent(ToDeviceEvent event) async { if (event.type == 'm.secret.request') { // got a request to share a secret diff --git a/test/encryption/key_verification_test.dart b/test/encryption/key_verification_test.dart index 319b8f5..8d7c86b 100644 --- a/test/encryption/key_verification_test.dart +++ b/test/encryption/key_verification_test.dart @@ -32,7 +32,7 @@ class MockSSSS extends SSSS { bool requestedSecrets = false; @override - Future maybeRequestAll(List devices) async { + Future maybeRequestAll([List devices]) async { requestedSecrets = true; final handle = open(); handle.unlock(recoveryKey: SSSS_KEY); diff --git a/test/encryption/ssss_test.dart b/test/encryption/ssss_test.dart index e698e09..a9b2593 100644 --- a/test/encryption/ssss_test.dart +++ b/test/encryption/ssss_test.dart @@ -30,6 +30,19 @@ import 'package:olm/olm.dart' as olm; import '../fake_client.dart'; import '../fake_matrix_api.dart'; +class MockSSSS extends SSSS { + MockSSSS(Encryption encryption) : super(encryption); + + bool requestedSecrets = false; + @override + Future maybeRequestAll([List devices]) async { + requestedSecrets = true; + final handle = open(); + handle.unlock(recoveryKey: SSSS_KEY); + await handle.maybeCacheAll(); + } +} + void main() { group('SSSS', () { var olmEnabled = true; @@ -392,6 +405,18 @@ void main() { expect(client.encryption.ssss.pendingShareRequests.length, 3); }); + test('periodicallyRequestMissingCache', () async { + client.userDeviceKeys[client.userID].masterKey.setDirectVerified(true); + client.encryption.ssss = MockSSSS(client.encryption); + (client.encryption.ssss as MockSSSS).requestedSecrets = false; + await client.encryption.ssss.periodicallyRequestMissingCache(); + expect((client.encryption.ssss as MockSSSS).requestedSecrets, true); + // it should only retry once every 15 min + (client.encryption.ssss as MockSSSS).requestedSecrets = false; + await client.encryption.ssss.periodicallyRequestMissingCache(); + expect((client.encryption.ssss as MockSSSS).requestedSecrets, false); + }); + test('dispose client', () async { await client.dispose(closeDatabase: true); });