From c1fb79a20fea24742603f3b46c02eff53b82379b Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Mon, 8 Aug 2022 18:47:43 +0200 Subject: [PATCH] feat: Implement a BTBV TrustManager --- lib/omemo_dart.dart | 2 +- lib/src/omemo/sessionmanager.dart | 2 + lib/src/trust/always.dart | 3 + lib/src/trust/base.dart | 4 ++ lib/src/trust/btbv.dart | 109 ++++++++++++++++++++++++++++++ test/trust_test.dart | 36 ++++++++++ 6 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 lib/src/trust/btbv.dart create mode 100644 test/trust_test.dart diff --git a/lib/omemo_dart.dart b/lib/omemo_dart.dart index 3307cfc..20750c7 100644 --- a/lib/omemo_dart.dart +++ b/lib/omemo_dart.dart @@ -12,5 +12,5 @@ export 'src/omemo/encryption_result.dart'; export 'src/omemo/fingerprint.dart'; export 'src/omemo/sessionmanager.dart'; export 'src/trust/base.dart'; -//export 'src/trust/btbv.dart'; +export 'src/trust/btbv.dart'; export 'src/x3dh/x3dh.dart'; diff --git a/lib/src/omemo/sessionmanager.dart b/lib/src/omemo/sessionmanager.dart index 69f379b..de7d7d7 100644 --- a/lib/src/omemo/sessionmanager.dart +++ b/lib/src/omemo/sessionmanager.dart @@ -134,6 +134,7 @@ class OmemoSessionManager { kexResult.ad, ); + await _trustManager.onNewSession(jid, deviceId); await _addSession(jid, deviceId, ratchet); return OmemoKeyExchange() @@ -177,6 +178,7 @@ class OmemoSessionManager { kexResult.ad, ); + await _trustManager.onNewSession(jid, deviceId); await _addSession(jid, deviceId, ratchet); } diff --git a/lib/src/trust/always.dart b/lib/src/trust/always.dart index 96572b5..45e08dd 100644 --- a/lib/src/trust/always.dart +++ b/lib/src/trust/always.dart @@ -8,4 +8,7 @@ import 'package:omemo_dart/src/trust/base.dart'; class AlwaysTrustingTrustManager extends TrustManager { @override Future isTrusted(String jid, int deviceId) async => true; + + @override + Future onNewSession(String jid, int deviceId) async {} } diff --git a/lib/src/trust/base.dart b/lib/src/trust/base.dart index 6ac5995..c4f1605 100644 --- a/lib/src/trust/base.dart +++ b/lib/src/trust/base.dart @@ -4,4 +4,8 @@ abstract class TrustManager { /// Return true when the device with id [deviceId] of Jid [jid] is trusted, i.e. if an /// encrypted message should be sent to this device. If not, return false. Future isTrusted(String jid, int deviceId); + + /// Called by the OmemoSessionManager when a new session has been built. Should set + /// a default trust state to [jid]'s device with identifier [deviceId]. + Future onNewSession(String jid, int deviceId); } diff --git a/lib/src/trust/btbv.dart b/lib/src/trust/btbv.dart new file mode 100644 index 0000000..bad3725 --- /dev/null +++ b/lib/src/trust/btbv.dart @@ -0,0 +1,109 @@ +import 'package:meta/meta.dart'; +import 'package:omemo_dart/src/omemo/ratchet_map_key.dart'; +import 'package:omemo_dart/src/trust/base.dart'; +import 'package:synchronized/synchronized.dart'; + +/// 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 +/// - verified: The fingerprint has been verified using OOB means +enum BTBVTrustState { + notTrusted, + blindTrust, + verified, +} + +class BlindTrustBeforeVerificationTrustManager extends TrustManager { + BlindTrustBeforeVerificationTrustManager() + : _trustCache = {}, + _devices = {}, + _lock = Lock(); + + /// The cache for Mapping a RatchetMapKey to its trust state + final Map _trustCache; + + /// Mapping of Jids to their device identifiers + final Map> _devices; + + /// The lock for _devices and _trustCache + final Lock _lock; + + /// Returns true if [jid] has at least one device that is verified. If not, returns false. + /// Note that this function accesses _devices and _trustCache, which requires that the + /// lock for those two maps (_lock) has been aquired before calling. + bool _hasAtLeastOneVerifiedDevice(String jid) { + if (!_devices.containsKey(jid)) return false; + + return _devices[jid]!.any((id) { + return _trustCache[RatchetMapKey(jid, id)]! == BTBVTrustState.verified; + }); + } + + @override + Future isTrusted(String jid, int deviceId) async { + var returnValue = false; + await _lock.synchronized(() async { + final trustCacheValue = _trustCache[RatchetMapKey(jid, deviceId)]; + if (trustCacheValue == BTBVTrustState.notTrusted) { + returnValue = false; + return; + } else if (trustCacheValue == BTBVTrustState.verified) { + // The key is verified, so it's safe. + returnValue = true; + return; + } else { + if (_hasAtLeastOneVerifiedDevice(jid)) { + // Do not trust if there is at least one device with full trust + returnValue = false; + return; + } else { + // We have not verified a key from [jid], so it is blind trust all the way. + returnValue = true; + return; + } + } + }); + + return returnValue; + } + + @override + Future onNewSession(String jid, int deviceId) async { + await _lock.synchronized(() async { + if (_hasAtLeastOneVerifiedDevice(jid)) { + _trustCache[RatchetMapKey(jid, deviceId)] = BTBVTrustState.notTrusted; + } else { + _trustCache[RatchetMapKey(jid, deviceId)] = BTBVTrustState.blindTrust; + } + + if (_devices.containsKey(jid)) { + _devices[jid]!.add(deviceId); + } else { + _devices[jid] = List.from([deviceId]); + } + }); + } + + /// Returns a mapping from the device identifiers of [jid] to their trust state. + Future> getDevicesTrust(String jid) async { + final map = {}; + + await _lock.synchronized(() async { + for (final deviceId in _devices[jid]!) { + map[deviceId] = _trustCache[RatchetMapKey(jid, deviceId)]!; + } + }); + + return map; + } + + /// Sets the trust of [jid]'s device with identifier [deviceId] to [state]. + Future setDeviceTrust(String jid, int deviceId, BTBVTrustState state) async { + await _lock.synchronized(() async { + _trustCache[RatchetMapKey(jid, deviceId)] = state; + }); + } + + @visibleForTesting + BTBVTrustState getDeviceTrust(String jid, int deviceId) => _trustCache[RatchetMapKey(jid, deviceId)]!; +} diff --git a/test/trust_test.dart b/test/trust_test.dart new file mode 100644 index 0000000..5efaeb5 --- /dev/null +++ b/test/trust_test.dart @@ -0,0 +1,36 @@ +import 'package:omemo_dart/omemo_dart.dart'; +import 'package:test/test.dart'; + +void main() { + test('Test the Blind Trust Before Verification TrustManager', () async { + // Caroline's BTBV manager + final btbv = BlindTrustBeforeVerificationTrustManager(); + // Example data + const aliceJid = 'alice@some.server'; + const bobJid = 'bob@other.server'; + + // Caroline starts a chat a device from Alice + await btbv.onNewSession(aliceJid, 1); + expect(await btbv.isTrusted(aliceJid, 1), true); + + // Caroline meets with Alice and verifies her fingerprint + await btbv.setDeviceTrust(aliceJid, 1, BTBVTrustState.verified); + expect(await btbv.isTrusted(aliceJid, 1), true); + + // Alice adds a new device + await btbv.onNewSession(aliceJid, 2); + expect(await btbv.isTrusted(aliceJid, 2), false); + expect(btbv.getDeviceTrust(aliceJid, 2), BTBVTrustState.notTrusted); + + // Caronline starts a chat with Bob but since they live far apart, Caroline cannot + // verify his fingerprint. + await btbv.onNewSession(bobJid, 3); + + // Bob adds a new device + await btbv.onNewSession(bobJid, 4); + expect(await btbv.isTrusted(bobJid, 3), true); + expect(await btbv.isTrusted(bobJid, 4), true); + expect(btbv.getDeviceTrust(bobJid, 3), BTBVTrustState.blindTrust); + expect(btbv.getDeviceTrust(bobJid, 4), BTBVTrustState.blindTrust); + }); +}