feat: Allow serialising and deserialising OmemoSessionManager
This commit is contained in:
parent
3b8ccfaccf
commit
859f25d867
@ -293,4 +293,23 @@ class OmemoDoubleRatchet {
|
||||
|
||||
return decrypt(mk, ciphertext, concat([sessionAd, header.writeToBuffer()]), sessionAd);
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
Future<bool> equals(OmemoDoubleRatchet other) async {
|
||||
// ignore: invalid_use_of_visible_for_testing_member
|
||||
final dhrMatch = dhr == null ? other.dhr == null : await dhr!.equals(other.dhr!);
|
||||
final ckrMatch = ckr == null ? other.ckr == null : listsEqual(ckr!, other.ckr!);
|
||||
final cksMatch = cks == null ? other.cks == null : listsEqual(cks!, other.cks!);
|
||||
|
||||
// ignore: invalid_use_of_visible_for_testing_member
|
||||
return await dhs.equals(other.dhs) &&
|
||||
dhrMatch &&
|
||||
listsEqual(rk, other.rk) &&
|
||||
cksMatch &&
|
||||
ckrMatch &&
|
||||
ns == other.ns &&
|
||||
nr == other.nr &&
|
||||
pn == other.pn &&
|
||||
listsEqual(sessionAd, other.sessionAd);
|
||||
}
|
||||
}
|
||||
|
@ -166,4 +166,31 @@ class Device {
|
||||
'opks': serialisedOpks,
|
||||
};
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
Future<bool> equals(Device other) async {
|
||||
var opksMatch = true;
|
||||
if (opks.length != other.opks.length) {
|
||||
opksMatch = false;
|
||||
} else {
|
||||
for (final entry in opks.entries) {
|
||||
// ignore: invalid_use_of_visible_for_testing_member
|
||||
final matches = await other.opks[entry.key]?.equals(entry.value) ?? false;
|
||||
if (!matches) {
|
||||
opksMatch = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: invalid_use_of_visible_for_testing_member
|
||||
final ikMatch = await ik.equals(other.ik);
|
||||
// ignore: invalid_use_of_visible_for_testing_member
|
||||
final spkMatch = await spk.equals(other.spk);
|
||||
return id == other.id &&
|
||||
ikMatch &&
|
||||
spkMatch &&
|
||||
listsEqual(spkSignature, other.spkSignature) &&
|
||||
spkId == other.spkId &&
|
||||
opksMatch;
|
||||
}
|
||||
}
|
||||
|
@ -60,19 +60,34 @@ class RatchetMapKey {
|
||||
|
||||
class OmemoSessionManager {
|
||||
|
||||
OmemoSessionManager(this._device)
|
||||
: _ratchetMap = {},
|
||||
_deviceMap = {},
|
||||
_lock = Lock(),
|
||||
OmemoSessionManager(this._device, this._deviceMap, this._ratchetMap)
|
||||
: _lock = Lock(),
|
||||
_deviceLock = Lock(),
|
||||
_eventStreamController = StreamController<OmemoEvent>.broadcast();
|
||||
|
||||
/// Deserialise the OmemoSessionManager from JSON data [data].
|
||||
factory OmemoSessionManager.fromJson(Map<String, dynamic> data) {
|
||||
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;
|
||||
}
|
||||
|
||||
// TODO(PapaTutuWawa): Handle Trust behaviour
|
||||
return OmemoSessionManager(
|
||||
Device.fromJson(data['device']! as Map<String, dynamic>),
|
||||
data['devices']! as Map<String, List<int>>,
|
||||
ratchetMap,
|
||||
);
|
||||
}
|
||||
|
||||
/// Generate a new cryptographic identity.
|
||||
static Future<OmemoSessionManager> generateNewIdentity({ int opkAmount = 100 }) async {
|
||||
assert(opkAmount > 0, 'opkAmount must be bigger than 0.');
|
||||
final device = await Device.generateNewDevice(opkAmount: opkAmount);
|
||||
|
||||
return OmemoSessionManager(device);
|
||||
return OmemoSessionManager(device, {}, {});
|
||||
}
|
||||
|
||||
/// Lock for _ratchetMap and _bundleMap
|
||||
@ -128,7 +143,8 @@ class OmemoSessionManager {
|
||||
|
||||
/// Create a ratchet session initiated by Alice to the user with Jid [jid] and the device
|
||||
/// [deviceId] from the bundle [bundle].
|
||||
Future<OmemoKeyExchange> _addSessionFromBundle(String jid, int deviceId, OmemoBundle bundle) async {
|
||||
@visibleForTesting
|
||||
Future<OmemoKeyExchange> addSessionFromBundle(String jid, int deviceId, OmemoBundle bundle) async {
|
||||
final device = await getDevice();
|
||||
final kexResult = await x3dhFromBundle(
|
||||
bundle,
|
||||
@ -191,7 +207,7 @@ class OmemoSessionManager {
|
||||
final kex = <int, OmemoKeyExchange>{};
|
||||
if (newSessions != null) {
|
||||
for (final newSession in newSessions) {
|
||||
kex[newSession.id] = await _addSessionFromBundle(jid, newSession.id, newSession);
|
||||
kex[newSession.id] = await addSessionFromBundle(jid, newSession.id, newSession);
|
||||
}
|
||||
}
|
||||
|
||||
@ -299,4 +315,49 @@ class OmemoSessionManager {
|
||||
|
||||
@visibleForTesting
|
||||
OmemoDoubleRatchet getRatchet(String jid, int deviceId) => _ratchetMap[RatchetMapKey(jid, deviceId)]!;
|
||||
|
||||
@visibleForTesting
|
||||
Map<String, List<int>> getDeviceMap() => _deviceMap;
|
||||
|
||||
@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': { ... },
|
||||
},
|
||||
...
|
||||
],
|
||||
'trust': { ... }
|
||||
}
|
||||
*/
|
||||
|
||||
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,
|
||||
// TODO(PapaTutuWawa): Implement
|
||||
'trust': <String, dynamic>{},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -9,17 +9,7 @@ void main() {
|
||||
final serialised = await oldDevice.toJson();
|
||||
|
||||
final newDevice = Device.fromJson(serialised);
|
||||
expect(oldDevice.id, newDevice.id);
|
||||
expect(await oldDevice.ik.equals(newDevice.ik), true);
|
||||
expect(await oldDevice.spk.equals(newDevice.spk), true);
|
||||
expect(listsEqual(oldDevice.spkSignature, newDevice.spkSignature), true);
|
||||
expect(oldDevice.spkId, newDevice.spkId);
|
||||
|
||||
// Check the Ontime-Prekeys
|
||||
expect(oldDevice.opks.length, newDevice.opks.length);
|
||||
for (final entry in oldDevice.opks.entries) {
|
||||
expect(await newDevice.opks[entry.key]!.equals(entry.value), true);
|
||||
}
|
||||
expect(await oldDevice.equals(newDevice), true);
|
||||
});
|
||||
|
||||
test('Test serialising and deserialising the OmemoDoubleRatchet', () async {
|
||||
@ -45,26 +35,35 @@ void main() {
|
||||
final aliceSerialised = await aliceOld.toJson();
|
||||
final aliceNew = OmemoDoubleRatchet.fromJson(aliceSerialised);
|
||||
|
||||
expect(await aliceOld.dhs.equals(aliceNew.dhs), true);
|
||||
if (aliceOld.dhr == null) {
|
||||
expect(aliceNew.dhr, null);
|
||||
} else {
|
||||
expect(await aliceOld.dhr!.equals(aliceNew.dhr!), true);
|
||||
expect(await aliceOld.equals(aliceNew), true);
|
||||
});
|
||||
|
||||
test('Test serialising and deserialising the OmemoSessionManager', () async {
|
||||
// Generate a random session
|
||||
final oldSession = await OmemoSessionManager.generateNewIdentity(opkAmount: 4);
|
||||
final bobSession = await OmemoSessionManager.generateNewIdentity(opkAmount: 4);
|
||||
await oldSession.addSessionFromBundle(
|
||||
'bob@localhost',
|
||||
(await bobSession.getDevice()).id,
|
||||
await (await bobSession.getDevice()).toBundle(),
|
||||
);
|
||||
|
||||
// Serialise and deserialise
|
||||
final serialised = await oldSession.toJson();
|
||||
final newSession = OmemoSessionManager.fromJson(serialised);
|
||||
|
||||
final oldDevice = await oldSession.getDevice();
|
||||
final newDevice = await newSession.getDevice();
|
||||
expect(await oldDevice.equals(newDevice), true);
|
||||
expect(oldSession.getDeviceMap(), 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);
|
||||
}
|
||||
expect(listsEqual(aliceOld.rk, aliceNew.rk), true);
|
||||
if (aliceOld.cks == null) {
|
||||
expect(aliceNew.cks, null);
|
||||
} else {
|
||||
expect(listsEqual(aliceOld.cks!, aliceNew.cks!), true);
|
||||
}
|
||||
if (aliceOld.ckr == null) {
|
||||
expect(aliceNew.ckr, null);
|
||||
} else {
|
||||
expect(listsEqual(aliceOld.ckr!, aliceNew.ckr!), true);
|
||||
}
|
||||
expect(aliceOld.ns, aliceNew.ns);
|
||||
expect(aliceOld.nr, aliceNew.nr);
|
||||
expect(aliceOld.pn, aliceNew.pn);
|
||||
expect(listsEqual(aliceOld.sessionAd, aliceNew.sessionAd), true);
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user