diff --git a/lib/src/omemo/sessionmanager.dart b/lib/src/omemo/sessionmanager.dart index 8aa243b..c803a5d 100644 --- a/lib/src/omemo/sessionmanager.dart +++ b/lib/src/omemo/sessionmanager.dart @@ -224,8 +224,11 @@ class OmemoSessionManager { // We assume that the user already checked if the session exists for (final jid in jids) { for (final deviceId in _deviceMap[jid]!) { - // Only encrypt to devices that are trusted - if (!(await _trustManager.isTrusted(jid, deviceId))) continue; + // Empty OMEMO messages are allowed to bypass trust + if (plaintext != null) { + // Only encrypt to devices that are trusted + if (!(await _trustManager.isTrusted(jid, deviceId))) continue; + } final ratchetKey = RatchetMapKey(jid, deviceId); final ratchet = _ratchetMap[ratchetKey]!; diff --git a/lib/src/trust/never.dart b/lib/src/trust/never.dart new file mode 100644 index 0000000..ec02bb6 --- /dev/null +++ b/lib/src/trust/never.dart @@ -0,0 +1,14 @@ +import 'package:meta/meta.dart'; +import 'package:omemo_dart/src/trust/base.dart'; + +/// Only use for testing! +/// An implementation of TrustManager that never trusts any device and thus +/// has no internal state. +@visibleForTesting +class NeverTrustingTrustManager extends TrustManager { + @override + Future isTrusted(String jid, int deviceId) async => false; + + @override + Future onNewSession(String jid, int deviceId) async {} +} diff --git a/test/omemo_test.dart b/test/omemo_test.dart index 7b4effb..5b6b20f 100644 --- a/test/omemo_test.dart +++ b/test/omemo_test.dart @@ -1,5 +1,6 @@ import 'package:omemo_dart/omemo_dart.dart'; import 'package:omemo_dart/src/trust/always.dart'; +import 'package:omemo_dart/src/trust/never.dart'; import 'package:test/test.dart'; void main() { @@ -336,4 +337,34 @@ void main() { ); expect(messagePlaintext, bobMessage); }); + + test('Test trust bypassing with empty OMEMO messages', () async { + const aliceJid = 'alice@server.example'; + const bobJid = 'bob@other.server.example'; + + // Alice and Bob generate their sessions + final aliceSession = await OmemoSessionManager.generateNewIdentity( + aliceJid, + NeverTrustingTrustManager(), + opkAmount: 1, + ); + final bobSession = await OmemoSessionManager.generateNewIdentity( + bobJid, + NeverTrustingTrustManager(), + opkAmount: 1, + ); + + // Alice encrypts an empty message for Bob + final aliceMessage = await aliceSession.encryptToJid( + bobJid, + null, + newSessions: [ + await (await bobSession.getDevice()).toBundle(), + ], + ); + + // Despite Alice not trusting Bob's device, we should have encrypted it for his + // untrusted device. + expect(aliceMessage.encryptedKeys.length, 1); + }); }