diff --git a/lib/src/omemo/ratchet_map_key.dart b/lib/src/omemo/ratchet_map_key.dart index f5aeb12..79ed90c 100644 --- a/lib/src/omemo/ratchet_map_key.dart +++ b/lib/src/omemo/ratchet_map_key.dart @@ -4,9 +4,24 @@ import 'package:meta/meta.dart'; class RatchetMapKey { const RatchetMapKey(this.jid, this.deviceId); + + factory RatchetMapKey.fromJsonKey(String key) { + final parts = key.split(':'); + final deviceId = int.parse(parts.first); + + return RatchetMapKey( + parts.sublist(1).join(':'), + deviceId, + ); + } + final String jid; final int deviceId; + String toJsonKey() { + return '$deviceId:$jid'; + } + @override bool operator ==(Object other) { return other is RatchetMapKey && jid == other.jid && deviceId == other.deviceId; diff --git a/lib/src/trust/always.dart b/lib/src/trust/always.dart index f929fc8..c91687a 100644 --- a/lib/src/trust/always.dart +++ b/lib/src/trust/always.dart @@ -17,4 +17,7 @@ class AlwaysTrustingTrustManager extends TrustManager { @override Future setEnabled(String jid, int deviceId, bool enabled) async {} + + @override + Future> toJson() async => {}; } diff --git a/lib/src/trust/base.dart b/lib/src/trust/base.dart index 8277e96..77d716b 100644 --- a/lib/src/trust/base.dart +++ b/lib/src/trust/base.dart @@ -16,4 +16,7 @@ abstract class TrustManager { /// Mark the device with id [deviceId] of Jid [jid] as enabled if [enabled] is true or as disabled /// if [enabled] is false. Future setEnabled(String jid, int deviceId, bool enabled); + + /// Serialize the trust manager to JSON. + Future> toJson(); } diff --git a/lib/src/trust/btbv.dart b/lib/src/trust/btbv.dart index dfbf672..2316d60 100644 --- a/lib/src/trust/btbv.dart +++ b/lib/src/trust/btbv.dart @@ -8,9 +8,26 @@ import 'package:synchronized/synchronized.dart'; /// - blindTrust: The fingerprint is not verified using OOB means /// - verified: The fingerprint has been verified using OOB means enum BTBVTrustState { - notTrusted, - blindTrust, - verified, + notTrusted, // = 1 + blindTrust, // = 2 + verified, // = 3 +} + +int _trustToInt(BTBVTrustState state) { + switch (state) { + case BTBVTrustState.notTrusted: return 1; + case BTBVTrustState.blindTrust: return 2; + case BTBVTrustState.verified: return 3; + } +} + +BTBVTrustState _trustFromInt(int i) { + switch (i) { + case 1: return BTBVTrustState.notTrusted; + case 2: return BTBVTrustState.blindTrust; + case 3: return BTBVTrustState.verified; + default: return BTBVTrustState.notTrusted; + } } /// A TrustManager that implements the idea of Blind Trust Before Verification. @@ -23,14 +40,17 @@ abstract class BlindTrustBeforeVerificationTrustManager extends TrustManager { _lock = Lock(); /// The cache for mapping a RatchetMapKey to its trust state + @visibleForTesting @protected final Map trustCache; /// The cache for mapping a RatchetMapKey to whether it is enabled or not + @visibleForTesting @protected final Map enablementCache; /// Mapping of Jids to their device identifiers + @visibleForTesting @protected final Map> devices; @@ -141,6 +161,50 @@ abstract class BlindTrustBeforeVerificationTrustManager extends TrustManager { // Commit the state await commitState(); } + + @override + Future> toJson() async { + return { + 'devices': devices, + 'trust': trustCache.map((key, value) => MapEntry( + key.toJsonKey(), _trustToInt(value), + ),), + 'enable': enablementCache.map((key, value) => MapEntry(key.toJsonKey(), value)), + }; + } + + /// From a serialized version of a BTBV trust manager, extract the device list. + /// NOTE: This is needed as Dart cannot just cast a List to List and so on. + static Map> deviceListFromJson(Map json) { + return (json['devices']! as Map).map>( + (key, value) => MapEntry( + key, + (value as List).map((i) => i as int).toList(), + ), + ); + } + + /// From a serialized version of a BTBV trust manager, extract the trust cache. + /// NOTE: This is needed as Dart cannot just cast a List to List and so on. + static Map trustCacheFromJson(Map json) { + return (json['trust']! as Map).map( + (key, value) => MapEntry( + RatchetMapKey.fromJsonKey(key), + _trustFromInt(value as int), + ), + ); + } + + /// From a serialized version of a BTBV trust manager, extract the enable cache. + /// NOTE: This is needed as Dart cannot just cast a List to List and so on. + static Map enableCacheFromJson(Map json) { + return (json['enable']! as Map).map( + (key, value) => MapEntry( + RatchetMapKey.fromJsonKey(key), + value as bool, + ), + ); + } /// Called when the state of the trust manager has been changed. Allows the user to /// commit the trust state to persistent storage. diff --git a/lib/src/trust/never.dart b/lib/src/trust/never.dart index d706734..fdc829c 100644 --- a/lib/src/trust/never.dart +++ b/lib/src/trust/never.dart @@ -17,4 +17,7 @@ class NeverTrustingTrustManager extends TrustManager { @override Future setEnabled(String jid, int deviceId, bool enabled) async {} + + @override + Future> toJson() async => {}; } diff --git a/test/serialisation_test.dart b/test/serialisation_test.dart index c280884..815f36f 100644 --- a/test/serialisation_test.dart +++ b/test/serialisation_test.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'package:omemo_dart/omemo_dart.dart'; import 'package:omemo_dart/src/trust/always.dart'; import 'package:test/test.dart'; @@ -104,4 +105,84 @@ void main() { expect(await oldRatchet.equals(newRatchet), true); } }); + + test('Test serialising and deserialising the BlindTrustBeforeVerificationTrustManager', () async { + // Caroline's BTBV manager + final btbv = MemoryBTBVTrustManager(); + // 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); + expect(await btbv.isEnabled(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); + expect(await btbv.isEnabled(aliceJid, 2), false); + + // 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); + expect(await btbv.isEnabled(bobJid, 3), true); + expect(await btbv.isEnabled(bobJid, 4), true); + }); + + test('Test serializing and deserializing RatchetMapKey', () { + const test1 = RatchetMapKey('user@example.org', 1234); + final result1 = RatchetMapKey.fromJsonKey(test1.toJsonKey()); + expect(result1.jid, test1.jid); + expect(result1.deviceId, test1.deviceId); + + const test2 = RatchetMapKey('user@example.org/hallo:welt', 3333); + final result2 = RatchetMapKey.fromJsonKey(test2.toJsonKey()); + expect(result2.jid, test2.jid); + expect(result2.deviceId, test2.deviceId); + }); + + test('Test serializing and deserializing the components of the BTBV manager', () async { + // Caroline's BTBV manager + final btbv = MemoryBTBVTrustManager(); + // Example data + const aliceJid = 'alice@some.server'; + const bobJid = 'bob@other.server'; + + await btbv.onNewSession(aliceJid, 1); + await btbv.setDeviceTrust(aliceJid, 1, BTBVTrustState.verified); + await btbv.onNewSession(aliceJid, 2); + await btbv.onNewSession(bobJid, 3); + await btbv.onNewSession(bobJid, 4); + + final managerJson = await btbv.toJson(); + final managerString = jsonEncode(managerJson); + final managerPostJson = jsonDecode(managerString) as Map; + final deviceList = BlindTrustBeforeVerificationTrustManager.deviceListFromJson( + managerPostJson, + ); + expect(btbv.devices, deviceList); + + final trustCache = BlindTrustBeforeVerificationTrustManager.trustCacheFromJson( + managerPostJson, + ); + expect(btbv.trustCache, trustCache); + + final enableCache = BlindTrustBeforeVerificationTrustManager.enableCacheFromJson( + managerPostJson, + ); + expect(btbv.enablementCache, enableCache); + }); }