Compare commits

...

4 Commits

7 changed files with 77 additions and 134 deletions

View File

@ -94,11 +94,20 @@ class OmemoDoubleRatchet {
]
}
*/
final mkSkipped = <SkippedKey, List<int>>{};
for (final entry in data['mkskipped']! as List<Map<String, dynamic>>) {
final key = SkippedKey.fromJson(entry);
mkSkipped[key] = base64.decode(entry['key']! as String);
}
// NOTE: Dart has some issues with just casting a List<dynamic> to List<Map<...>>, as
// such we need to convert the items by hand.
final mkSkipped = Map<SkippedKey, List<int>>.fromEntries(
(data['mkskipped']! as List<dynamic>).map<MapEntry<SkippedKey, List<int>>>(
(entry) {
final map = entry as Map<String, dynamic>;
final key = SkippedKey.fromJson(map);
return MapEntry(
key,
base64.decode(map['key']! as String),
);
},
),
);
return OmemoDoubleRatchet(
OmemoKeyPair.fromBytes(

View File

@ -49,14 +49,23 @@ class Device {
]
}
*/
final opks = <int, OmemoKeyPair>{};
for (final opk in data['opks']! as List<Map<String, dynamic>>) {
opks[opk['id']! as int] = OmemoKeyPair.fromBytes(
base64.decode(opk['public']! as String),
base64.decode(opk['private']! as String),
KeyPairType.x25519,
);
}
// NOTE: Dart has some issues with just casting a List<dynamic> to List<Map<...>>, as
// such we need to convert the items by hand.
final opks = Map<int, OmemoKeyPair>.fromEntries(
(data['opks']! as List<dynamic>).map<MapEntry<int, OmemoKeyPair>>(
(opk) {
final map = opk as Map<String, dynamic>;
return MapEntry(
map['id']! as int,
OmemoKeyPair.fromBytes(
base64.decode(map['public']! as String),
base64.decode(map['private']! as String),
KeyPairType.x25519,
),
);
},
),
);
return Device(
data['jid']! as String,

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:cryptography/cryptography.dart';
import 'package:hex/hex.dart';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:omemo_dart/src/crypto.dart';
import 'package:omemo_dart/src/double_ratchet/double_ratchet.dart';
@ -31,31 +32,28 @@ class OmemoSessionManager {
OmemoSessionManager(this._device, this._deviceMap, this._ratchetMap, this._trustManager)
: _lock = Lock(),
_deviceLock = Lock(),
_eventStreamController = StreamController<OmemoEvent>.broadcast();
/// Deserialise the OmemoSessionManager from JSON data [data].
factory OmemoSessionManager.fromJson(Map<String, dynamic> data, TrustManager trustManager) {
final ratchetMap = <RatchetMapKey, OmemoDoubleRatchet>{};
for (final rawRatchet in data['sessions']! as List<Map<String, dynamic>>) {
final key = RatchetMapKey(rawRatchet['jid']! as String, rawRatchet['deviceId']! as int);
final ratchet = OmemoDoubleRatchet.fromJson(rawRatchet['ratchet']! as Map<String, dynamic>);
ratchetMap[key] = ratchet;
}
return OmemoSessionManager(
Device.fromJson(data['device']! as Map<String, dynamic>),
data['devices']! as Map<String, List<int>>,
ratchetMap,
trustManager,
);
}
_eventStreamController = StreamController<OmemoEvent>.broadcast(),
_log = Logger('OmemoSessionManager');
/// Deserialise the OmemoSessionManager from JSON data [data] that does not contain
/// the ratchet sessions.
factory OmemoSessionManager.fromJsonWithoutSessions(Map<String, dynamic> data, Map<RatchetMapKey, OmemoDoubleRatchet> ratchetMap, TrustManager trustManager) {
factory OmemoSessionManager.fromJsonWithoutSessions(
Map<String, dynamic> data,
Map<RatchetMapKey, OmemoDoubleRatchet> ratchetMap,
TrustManager trustManager,
) {
// NOTE: Dart has some issues with just casting a List<dynamic> to List<Map<...>>, as
// such we need to convert the items by hand.
return OmemoSessionManager(
Device.fromJson(data['device']! as Map<String, dynamic>),
data['devices']! as Map<String, List<int>>,
(data['devices']! as Map<String, dynamic>).map<String, List<int>>(
(key, value) {
return MapEntry(
key,
(value as List<dynamic>).map<int>((i) => i as int).toList(),
);
}
),
ratchetMap,
trustManager,
);
@ -68,6 +66,9 @@ class OmemoSessionManager {
return OmemoSessionManager(device, {}, {}, trustManager);
}
/// Logging
Logger _log;
/// Lock for _ratchetMap and _bundleMap
final Lock _lock;
@ -304,7 +305,7 @@ class OmemoSessionManager {
/// [mapKey] with [oldRatchet].
Future<void> _restoreRatchet(RatchetMapKey mapKey, OmemoDoubleRatchet oldRatchet) async {
await _lock.synchronized(() {
print('RESTORING RATCHETS');
_log.finest('Restoring ratchet ${mapKey.jid}:${mapKey.deviceId}');
_ratchetMap[mapKey] = oldRatchet;
// Commit the ratchet
@ -561,42 +562,6 @@ class OmemoSessionManager {
@visibleForTesting
Map<RatchetMapKey, OmemoDoubleRatchet> getRatchetMap() => _ratchetMap;
/// Serialise the entire session manager into a JSON object.
Future<Map<String, dynamic>> toJson() async {
/*
{
'devices': {
'alice@...': [1, 2, ...],
'bob@...': [1],
...
},
'device': { ... },
'sessions': [
{
'jid': 'alice@...',
'deviceId': 1,
'ratchet': { ... },
},
...
],
}
*/
final sessions = List<Map<String, dynamic>>.empty(growable: true);
for (final entry in _ratchetMap.entries) {
sessions.add({
'jid': entry.key.jid,
'deviceId': entry.key.deviceId,
'ratchet': await entry.value.toJson(),
});
}
return {
'devices': _deviceMap,
'device': await (await getDevice()).toJson(),
'sessions': sessions,
};
}
/// Serialise the entire session manager into a JSON object.
Future<Map<String, dynamic>> toJsonWithoutSessions() async {
/*

View File

@ -214,11 +214,6 @@ abstract class BlindTrustBeforeVerificationTrustManager extends TrustManager {
@visibleForOverriding
Future<void> commitState();
/// Called when the user wants to restore the state of the trust manager. The format
/// and actual storage mechanism is left to the user.
@visibleForOverriding
Future<void> loadState();
@visibleForTesting
BTBVTrustState getDeviceTrust(String jid, int deviceId) => trustCache[RatchetMapKey(jid, deviceId)]!;
}
@ -228,7 +223,4 @@ abstract class BlindTrustBeforeVerificationTrustManager extends TrustManager {
class MemoryBTBVTrustManager extends BlindTrustBeforeVerificationTrustManager {
@override
Future<void> commitState() async {}
@override
Future<void> loadState() async {}
}

View File

@ -11,6 +11,7 @@ dependencies:
collection: ^1.16.0
cryptography: ^2.0.5
hex: ^0.2.0
logging: ^1.0.2
meta: ^1.7.0
pinenacl: ^0.5.1
synchronized: ^3.0.0+2

View File

@ -1,9 +1,17 @@
import 'package:logging/logging.dart';
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() {
Logger.root
..level = Level.ALL
..onRecord.listen((record) {
// ignore: avoid_print
print('${record.level.name}: ${record.message}');
});
test('Test using OMEMO sessions with only one device per user', () async {
const aliceJid = 'alice@server.example';
const bobJid = 'bob@other.server.example';

View File

@ -3,6 +3,10 @@ import 'package:omemo_dart/omemo_dart.dart';
import 'package:omemo_dart/src/trust/always.dart';
import 'package:test/test.dart';
Map<String, dynamic> jsonify(Map<String, dynamic> map) {
return jsonDecode(jsonEncode(map)) as Map<String, dynamic>;
}
void main() {
test('Test serialising and deserialising the Device', () async {
// Generate a random session
@ -12,7 +16,7 @@ void main() {
opkAmount: 1,
);
final oldDevice = await oldSession.getDevice();
final serialised = await oldDevice.toJson();
final serialised = jsonify(await oldDevice.toJson());
final newDevice = Device.fromJson(serialised);
expect(await oldDevice.equals(newDevice), true);
@ -26,7 +30,7 @@ void main() {
opkAmount: 1,
);
final oldDevice = await (await oldSession.getDevice()).replaceSignedPrekey();
final serialised = await oldDevice.toJson();
final serialised = jsonify(await oldDevice.toJson());
final newDevice = Device.fromJson(serialised);
expect(await oldDevice.equals(newDevice), true);
@ -60,7 +64,7 @@ void main() {
aliceMessage.encryptedKeys,
);
final aliceOld = aliceSession.getRatchet(bobJid, await bobSession.getDeviceId());
final aliceSerialised = await aliceOld.toJson();
final aliceSerialised = jsonify(await aliceOld.toJson());
final aliceNew = OmemoDoubleRatchet.fromJson(aliceSerialised);
expect(await aliceOld.equals(aliceNew), true);
@ -85,9 +89,11 @@ void main() {
);
// Serialise and deserialise
final serialised = await oldSession.toJson();
final newSession = OmemoSessionManager.fromJson(
final serialised = jsonify(await oldSession.toJsonWithoutSessions());
final newSession = OmemoSessionManager.fromJsonWithoutSessions(
serialised,
// NOTE: At this point, we don't care about this attribute
{},
AlwaysTrustingTrustManager(),
);
@ -95,51 +101,6 @@ void main() {
final newDevice = await newSession.getDevice();
expect(await oldDevice.equals(newDevice), true);
expect(await oldSession.getDeviceMap(), await newSession.getDeviceMap());
expect(oldSession.getRatchetMap().length, newSession.getRatchetMap().length);
for (final session in oldSession.getRatchetMap().entries) {
expect(newSession.getRatchetMap().containsKey(session.key), true);
final oldRatchet = oldSession.getRatchetMap()[session.key]!;
final newRatchet = newSession.getRatchetMap()[session.key]!;
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', () {
@ -167,21 +128,19 @@ void main() {
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<String, dynamic>;
final serialized = jsonify(await btbv.toJson());
final deviceList = BlindTrustBeforeVerificationTrustManager.deviceListFromJson(
managerPostJson,
serialized,
);
expect(btbv.devices, deviceList);
final trustCache = BlindTrustBeforeVerificationTrustManager.trustCacheFromJson(
managerPostJson,
serialized,
);
expect(btbv.trustCache, trustCache);
final enableCache = BlindTrustBeforeVerificationTrustManager.enableCacheFromJson(
managerPostJson,
serialized,
);
expect(btbv.enablementCache, enableCache);
});