22 Commits

Author SHA1 Message Date
b6aa28e1ee release: Bump version 2022-08-19 17:00:44 +02:00
2e10842c54 feat: Make accepted ratchets unacknowledged by default 2022-08-19 16:59:24 +02:00
0e2af1f2a3 feat: Add a function to check if a ratchet is acknowledged 2022-08-19 16:58:23 +02:00
80e1b20f27 fix: Fix crash when calling getUnacknowledgedRatchets for a new Jid 2022-08-18 16:21:53 +02:00
f68e45af26 docs: Update README 2022-08-18 15:35:07 +02:00
345596923e release: Bump version to 0.2.0 2022-08-18 15:33:49 +02:00
d5d4aa9014 feat: Add getDeviceId and getDeviceBundle 2022-08-18 15:30:31 +02:00
ee7b09bdb0 feat: Ratchets should overwrite each other 2022-08-18 15:20:32 +02:00
73613e266f feat: Allow regerating a device's id 2022-08-18 15:08:05 +02:00
0a03483aaf feat: Allow regenerating one's device identity 2022-08-18 15:02:17 +02:00
fda06cef55 feat: Implement acknowledging ratchet sessions 2022-08-16 14:02:04 +02:00
800b53b11f style: Fix linter warnings 2022-08-16 13:46:08 +02:00
5a097e4d2a feat: Allow removing a ratchet session 2022-08-16 13:27:21 +02:00
710b3c9497 feat: Allow serialising and deserialising without the ratchets 2022-08-16 12:57:16 +02:00
f540a80ec2 docs: Remove trust manager serialization from OmemoSessionManager 2022-08-16 12:54:15 +02:00
44ab31aebb release: Bump version to 0.1.3 2022-08-11 12:06:02 +02:00
e4f1d7d4b0 docs: Fix indent in README 2022-08-11 12:04:40 +02:00
7600804aa1 test: Uncomment those tests 2022-08-11 12:02:33 +02:00
a4589b6e09 feat: Allow access to the device map 2022-08-11 12:02:21 +02:00
d0986a4608 fix: Fix ratchet only working for one message 2022-08-11 11:57:33 +02:00
683a76cc80 test: Add test for sending multiple OMEMO messages 2022-08-10 17:24:35 +02:00
dad707f71d release: Sigh, bump to 0.1.2 2022-08-09 16:40:41 +02:00
11 changed files with 479 additions and 51 deletions

View File

@@ -5,6 +5,6 @@ line-length=72
[title-trailing-punctuation]
[title-hard-tab]
[title-match-regex]
regex=^(feat|fix|test|release|chore|security|docs|refactor):.*$
regex=^(feat|fix|test|release|chore|security|docs|refactor|style):.*$
[body-trailing-whitespace]
[body-first-line-empty]

View File

@@ -4,3 +4,19 @@
- Implement the Double Ratchet, X3DH and OMEMO specific bits
- Add a Blind-Trust-Before-Verification TrustManager
- Supported OMEMO version: 0.8.3
## 0.1.3
- Fix bug with the Double Ratchet causing only the initial message to be decryptable
- Expose `getDeviceMap` as a developer usable function
## 0.2.0
- Add convenience functions `getDeviceId` and `getDeviceBundle`
- Creating a new ratchet with an id for which we already have a ratchet will now overwrite the old ratchet
- Ratchet now carry an "acknowledged" attribute
## 0.2.1
- Add `isRatchetAcknowledged`
- Ratchets that are created due to accepting a kex are now unacknowledged

View File

@@ -26,10 +26,10 @@ Include `omemo_dart` in your `pubspec.yaml` like this:
# [...]
dependencies:
omemo_dart:
hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub
version: ^0.1.0
# [...]
omemo_dart:
hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub
version: ^0.2.0
# [...]
# [...]
```

View File

@@ -65,7 +65,7 @@ void main() async {
// a new session. Let's also assume that Bob only has one device. We may, however,
// add more bundles to newSessions, if we know of more.
newSessions: [
await (await bobSession.getDevice()).toBundle(),
await bobSession.getDeviceBundle(),
],
);

View File

@@ -67,6 +67,7 @@ class OmemoDoubleRatchet {
this.ik,
this.sessionAd,
this.mkSkipped, // MKSKIPPED
this.acknowledged,
);
factory OmemoDoubleRatchet.fromJson(Map<String, dynamic> data) {
@@ -83,6 +84,7 @@ class OmemoDoubleRatchet {
'pn': 0,
'ik_pub': 'base/64/encoded',
'session_ad': 'base/64/encoded',
'acknowledged': true | false,
'mkskipped': [
{
'key': 'base/64/encoded',
@@ -117,6 +119,7 @@ class OmemoDoubleRatchet {
),
base64.decode(data['session_ad']! as String),
mkSkipped,
data['acknowledged']! as bool,
);
}
@@ -148,6 +151,10 @@ class OmemoDoubleRatchet {
final Map<SkippedKey, List<int>> mkSkipped;
/// Indicates whether we received an empty OMEMO message after building a session with
/// the device.
bool acknowledged;
/// Create an OMEMO session using the Signed Pre Key [spk], the shared secret [sk] that
/// was obtained using a X3DH and the associated data [ad] that was also obtained through
/// a X3DH. [ik] refers to Bob's (the receiver's) IK public key.
@@ -169,6 +176,7 @@ class OmemoDoubleRatchet {
ik,
ad,
{},
false,
);
}
@@ -189,6 +197,7 @@ class OmemoDoubleRatchet {
ik,
ad,
{},
false,
);
}
@@ -214,6 +223,7 @@ class OmemoDoubleRatchet {
'ik_pub': base64.encode(await ik.getBytes()),
'session_ad': base64.encode(sessionAd),
'mkskipped': mkSkippedSerialised,
'acknowledged': acknowledged,
};
}
@@ -293,11 +303,15 @@ class OmemoDoubleRatchet {
return plaintext;
}
if (header.dhPub != await dhr?.getBytes()) {
final dhPubMatches = listsEqual(
header.dhPub ?? <int>[],
await dhr?.getBytes() ?? <int>[],
);
if (!dhPubMatches) {
await _skipMessageKeys(header.pn!);
await _dhRatchet(header);
}
await _skipMessageKeys(header.n!);
final newCkr = await kdfCk(ckr!, kdfCkNextChainKey);
final mk = await kdfCk(ckr!, kdfCkNextMessageKey);

View File

@@ -163,6 +163,23 @@ class Device {
);
}
/// Returns a new device that is equal to this one with the exception that the new
/// device's id is a new number between 0 and 2**32 - 1.
@internal
Device withNewId() {
return Device(
jid,
generateRandom32BitNumber(),
ik,
spk,
spkId,
spkSignature,
oldSpk,
oldSpkId,
opks,
);
}
/// Converts this device into an OmemoBundle that could be used for publishing.
Future<OmemoBundle> toBundle() async {
final encodedOpks = <int, String>{};

View File

@@ -12,6 +12,14 @@ class RatchetModifiedEvent extends OmemoEvent {
final OmemoDoubleRatchet ratchet;
}
/// Triggered when a ratchet has been removed and should be removed from storage.
class RatchetRemovedEvent extends OmemoEvent {
RatchetRemovedEvent(this.jid, this.deviceId);
final String jid;
final int deviceId;
}
/// Triggered when the device map has been modified
class DeviceMapModifiedEvent extends OmemoEvent {

View File

@@ -49,7 +49,18 @@ class OmemoSessionManager {
trustManager,
);
}
/// 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) {
return OmemoSessionManager(
Device.fromJson(data['device']! as Map<String, dynamic>),
data['devices']! as Map<String, List<int>>,
ratchetMap,
trustManager,
);
}
/// Generate a new cryptographic identity.
static Future<OmemoSessionManager> generateNewIdentity(String jid, TrustManager trustManager, { int opkAmount = 100 }) async {
assert(opkAmount > 0, 'opkAmount must be bigger than 0.');
@@ -83,6 +94,7 @@ class OmemoSessionManager {
/// A stream that receives events regarding the session
Stream<OmemoEvent> get eventStream => _eventStreamController.stream;
/// Returns our own device.
Future<Device> getDevice() async {
Device? dev;
await _deviceLock.synchronized(() async {
@@ -91,6 +103,18 @@ class OmemoSessionManager {
return dev!;
}
/// Returns the id attribute of our own device. This is just a short-hand for
/// ```await (session.getDevice()).id```.
Future<int> getDeviceId() async {
return _deviceLock.synchronized(() => _device.id);
}
/// Returns the device as an OmemoBundle. This is just a short-hand for
/// ```await (await session.getDevice()).toBundle()```.
Future<OmemoBundle> getDeviceBundle() async {
return _deviceLock.synchronized(() async => _device.toBundle());
}
/// Add a session [ratchet] with the [deviceId] to the internal tracking state.
Future<void> _addSession(String jid, int deviceId, OmemoDoubleRatchet ratchet) async {
@@ -98,21 +122,22 @@ class OmemoSessionManager {
// Add the bundle Id
if (!_deviceMap.containsKey(jid)) {
_deviceMap[jid] = [deviceId];
} else {
_deviceMap[jid]!.add(deviceId);
}
// Commit the device map
_eventStreamController.add(DeviceMapModifiedEvent(_deviceMap));
// Commit the device map
_eventStreamController.add(DeviceMapModifiedEvent(_deviceMap));
} else {
// Prevent having the same device multiple times in the list
if (!_deviceMap[jid]!.contains(deviceId)) {
_deviceMap[jid]!.add(deviceId);
// Commit the device map
_eventStreamController.add(DeviceMapModifiedEvent(_deviceMap));
}
}
// Add the ratchet session
final key = RatchetMapKey(jid, deviceId);
if (!_ratchetMap.containsKey(key)) {
_ratchetMap[key] = ratchet;
} else {
// TODO(PapaTutuWawa): What do we do now?
throw Exception();
}
_ratchetMap[key] = ratchet;
// Commit the ratchet
_eventStreamController.add(RatchetModifiedEvent(jid, deviceId, ratchet));
@@ -386,13 +411,98 @@ class OmemoSessionManager {
_eventStreamController.add(DeviceModifiedEvent(_device));
});
}
/// Returns the device map, i.e. the mapping of bare Jid to its device identifiers
/// we have built sessions with.
Future<Map<String, List<int>>> getDeviceMap() async {
Map<String, List<int>>? map;
await _lock.synchronized(() async {
map = _deviceMap;
});
return map!;
}
/// Removes the ratchet identified by [jid] and [deviceId] from the session manager.
/// Also triggers events for commiting the new device map to storage and removing
/// the old ratchet.
Future<void> removeRatchet(String jid, int deviceId) async {
await _lock.synchronized(() async {
// Remove the ratchet
_ratchetMap.remove(RatchetMapKey(jid, deviceId));
// Commit it
_eventStreamController.add(RatchetRemovedEvent(jid, deviceId));
// Remove the device from jid
_deviceMap[jid]!.remove(deviceId);
if (_deviceMap[jid]!.isEmpty) {
_deviceMap.remove(jid);
}
// Commit it
_eventStreamController.add(DeviceMapModifiedEvent(_deviceMap));
});
}
/// Returns the list of device identifiers belonging to [jid] that are yet unacked, i.e.
/// we have not yet received an empty OMEMO message from.
Future<List<int>?> getUnacknowledgedRatchets(String jid) async {
return _lock.synchronized(() async {
final ret = List<int>.empty(growable: true);
final devices = _deviceMap[jid];
if (devices == null) return null;
for (final device in devices) {
final ratchet = _ratchetMap[RatchetMapKey(jid, device)]!;
if (!ratchet.acknowledged) ret.add(device);
}
return ret;
});
}
/// Returns true if the ratchet for [jid] with device identifier [deviceId] is
/// acknowledged. Returns false if not.
Future<bool> isRatchetAcknowledged(String jid, int deviceId) async {
return _lock.synchronized(() => _ratchetMap[RatchetMapKey(jid, deviceId)]!.acknowledged);
}
/// Mark the ratchet for device [deviceId] from [jid] as acked.
Future<void> ratchetAcknowledged(String jid, int deviceId) async {
await _lock.synchronized(() async {
final ratchet = _ratchetMap[RatchetMapKey(jid, deviceId)]!
..acknowledged = true;
// Commit it
_eventStreamController.add(RatchetModifiedEvent(jid, deviceId, ratchet));
});
}
/// Generates an entirely new device. May be useful when the user wants to reset their cryptographic
/// identity. Triggers an event to commit it to storage.
Future<void> regenerateDevice({ int opkAmount = 100 }) async {
await _deviceLock.synchronized(() async {
_device = await Device.generateNewDevice(_device.jid, opkAmount: opkAmount);
// Commit it
_eventStreamController.add(DeviceModifiedEvent(_device));
});
}
/// Make our device have a new identifier. Only useful before publishing it as a bundle
/// to make sure that our device has a id that is account unique.
Future<void> regenerateDeviceId() async {
await _deviceLock.synchronized(() async {
_device = _device.withNewId();
// Commit it
_eventStreamController.add(DeviceModifiedEvent(_device));
});
}
@visibleForTesting
OmemoDoubleRatchet getRatchet(String jid, int deviceId) => _ratchetMap[RatchetMapKey(jid, deviceId)]!;
@visibleForTesting
Map<String, List<int>> getDeviceMap() => _deviceMap;
@visibleForTesting
Map<RatchetMapKey, OmemoDoubleRatchet> getRatchetMap() => _ratchetMap;
@@ -414,7 +524,6 @@ class OmemoSessionManager {
},
...
],
'trust': { ... }
}
*/
@@ -430,8 +539,25 @@ class OmemoSessionManager {
'devices': _deviceMap,
'device': await (await getDevice()).toJson(),
'sessions': sessions,
// TODO(PapaTutuWawa): Implement
'trust': <String, dynamic>{},
};
}
/// Serialise the entire session manager into a JSON object.
Future<Map<String, dynamic>> toJsonWithoutSessions() async {
/*
{
'devices': {
'alice@...': [1, 2, ...],
'bob@...': [1],
...
},
'device': { ... },
}
*/
return {
'devices': _deviceMap,
'device': await (await getDevice()).toJson(),
};
}
}

View File

@@ -1,6 +1,6 @@
name: omemo_dart
description: An XMPP library independent OMEMO library
version: 0.1.0
version: 0.2.1
homepage: https://github.com/PapaTutuWawa/omemo_dart
publish_to: https://git.polynom.me/api/packages/PapaTutuWawa/pub

View File

@@ -7,7 +7,6 @@ void main() {
test('Test using OMEMO sessions with only one device per user', () async {
const aliceJid = 'alice@server.example';
const bobJid = 'bob@other.server.example';
// Alice and Bob generate their sessions
var deviceModified = false;
var ratchetModified = 0;
@@ -39,7 +38,7 @@ void main() {
bobJid,
messagePlaintext,
newSessions: [
await (await bobSession.getDevice()).toBundle(),
await bobSession.getDeviceBundle(),
],
);
expect(aliceMessage.encryptedKeys.length, 1);
@@ -51,7 +50,7 @@ void main() {
final bobMessage = await bobSession.decryptMessage(
aliceMessage.ciphertext,
aliceJid,
(await aliceSession.getDevice()).id,
await aliceSession.getDeviceId(),
aliceMessage.encryptedKeys,
);
expect(messagePlaintext, bobMessage);
@@ -82,7 +81,7 @@ void main() {
final aliceReceivedMessage = await aliceSession.decryptMessage(
bobResponseMessage.ciphertext,
bobJid,
(await bobSession.getDevice()).id,
await bobSession.getDeviceId(),
bobResponseMessage.encryptedKeys,
);
expect(bobResponseText, aliceReceivedMessage);
@@ -116,8 +115,8 @@ void main() {
bobJid,
messagePlaintext,
newSessions: [
await (await bobSession.getDevice()).toBundle(),
await (await bobSession2.getDevice()).toBundle(),
await bobSession.getDeviceBundle(),
await bobSession2.getDeviceBundle(),
],
);
expect(aliceMessage.encryptedKeys.length, 2);
@@ -131,7 +130,7 @@ void main() {
final bobMessage = await bobSession.decryptMessage(
aliceMessage.ciphertext,
aliceJid,
(await aliceSession.getDevice()).id,
await aliceSession.getDeviceId(),
aliceMessage.encryptedKeys,
);
expect(messagePlaintext, bobMessage);
@@ -150,7 +149,7 @@ void main() {
final aliceReceivedMessage = await aliceSession.decryptMessage(
bobResponseMessage.ciphertext,
bobJid,
(await bobSession.getDevice()).id,
await bobSession.getDeviceId(),
bobResponseMessage.encryptedKeys,
);
expect(bobResponseText, aliceReceivedMessage);
@@ -193,8 +192,8 @@ void main() {
[bobJid, aliceJid],
messagePlaintext,
newSessions: [
await (await bobSession.getDevice()).toBundle(),
await (await aliceSession2.getDevice()).toBundle(),
await bobSession.getDeviceBundle(),
await aliceSession2.getDeviceBundle(),
],
);
expect(aliceMessage.encryptedKeys.length, 2);
@@ -206,7 +205,7 @@ void main() {
final bobMessage = await bobSession.decryptMessage(
aliceMessage.ciphertext,
aliceJid,
(await aliceSession1.getDevice()).id,
await aliceSession1.getDeviceId(),
aliceMessage.encryptedKeys,
);
expect(messagePlaintext, bobMessage);
@@ -215,7 +214,7 @@ void main() {
final aliceMessage2 = await aliceSession2.decryptMessage(
aliceMessage.ciphertext,
aliceJid,
(await aliceSession1.getDevice()).id,
await aliceSession1.getDeviceId(),
aliceMessage.encryptedKeys,
);
expect(messagePlaintext, aliceMessage2);
@@ -242,7 +241,7 @@ void main() {
bobJid,
null,
newSessions: [
await (await bobSession.getDevice()).toBundle(),
await bobSession.getDeviceBundle(),
],
);
expect(aliceMessage.encryptedKeys.length, 1);
@@ -255,13 +254,13 @@ void main() {
final bobMessage = await bobSession.decryptMessage(
aliceMessage.ciphertext,
aliceJid,
(await aliceSession.getDevice()).id,
await aliceSession.getDeviceId(),
aliceMessage.encryptedKeys,
);
expect(bobMessage, null);
// This call must not cause an exception
bobSession.getRatchet(aliceJid, (await aliceSession.getDevice()).id);
bobSession.getRatchet(aliceJid, await aliceSession.getDeviceId());
});
test('Test rotating the Signed Prekey', () async {
@@ -317,7 +316,7 @@ void main() {
bobJid,
messagePlaintext,
newSessions: [
await (await bobSession.getDevice()).toBundle(),
await bobSession.getDeviceBundle(),
],
);
expect(aliceMessage.encryptedKeys.length, 1);
@@ -332,7 +331,7 @@ void main() {
final bobMessage = await bobSession.decryptMessage(
aliceMessage.ciphertext,
aliceJid,
(await aliceSession.getDevice()).id,
await aliceSession.getDeviceId(),
aliceMessage.encryptedKeys,
);
expect(messagePlaintext, bobMessage);
@@ -359,7 +358,7 @@ void main() {
bobJid,
null,
newSessions: [
await (await bobSession.getDevice()).toBundle(),
await bobSession.getDeviceBundle(),
],
);
@@ -367,4 +366,252 @@ void main() {
// untrusted device.
expect(aliceMessage.encryptedKeys.length, 1);
});
test('Test by sending multiple messages back and forth', () async {
const aliceJid = 'alice@server.example';
const bobJid = 'bob@other.server.example';
// Alice and Bob generate their sessions
final aliceSession = await OmemoSessionManager.generateNewIdentity(
aliceJid,
AlwaysTrustingTrustManager(),
opkAmount: 1,
);
final bobSession = await OmemoSessionManager.generateNewIdentity(
bobJid,
AlwaysTrustingTrustManager(),
opkAmount: 1,
);
// Alice encrypts a message for Bob
final aliceMessage = await aliceSession.encryptToJid(
bobJid,
'Hello Bob!',
newSessions: [
await bobSession.getDeviceBundle(),
],
);
// Alice sends the message to Bob
// ...
await bobSession.decryptMessage(
aliceMessage.ciphertext,
aliceJid,
await aliceSession.getDeviceId(),
aliceMessage.encryptedKeys,
);
for (var i = 0; i < 100; i++) {
final messageText = 'Test Message #$i';
// Bob responds to Alice
final bobResponseMessage = await bobSession.encryptToJid(
aliceJid,
messageText,
);
// Bob sends the message to Alice
// ...
// Alice decrypts it
final aliceReceivedMessage = await aliceSession.decryptMessage(
bobResponseMessage.ciphertext,
bobJid,
await bobSession.getDeviceId(),
bobResponseMessage.encryptedKeys,
);
expect(messageText, aliceReceivedMessage);
}
});
group('Test removing a ratchet', () {
test('Test removing a ratchet when the user has multiple', () async {
const aliceJid = 'alice@server.local';
const bobJid = 'bob@some.server.local';
final aliceSession = await OmemoSessionManager.generateNewIdentity(
aliceJid,
AlwaysTrustingTrustManager(),
opkAmount: 1,
);
final bobSession1 = await OmemoSessionManager.generateNewIdentity(
bobJid,
AlwaysTrustingTrustManager(),
opkAmount: 1,
);
final bobSession2 = await OmemoSessionManager.generateNewIdentity(
bobJid,
AlwaysTrustingTrustManager(),
opkAmount: 1,
);
// Alice sends a message to those two Bobs
await aliceSession.encryptToJid(
bobJid,
'Hallo Welt',
newSessions: [
await bobSession1.getDeviceBundle(),
await bobSession2.getDeviceBundle(),
],
);
// One of those two sessions is broken, so Alice removes the session2 ratchet
final id1 = await bobSession1.getDeviceId();
final id2 = await bobSession2.getDeviceId();
await aliceSession.removeRatchet(bobJid, id1);
final map = aliceSession.getRatchetMap();
expect(map.containsKey(RatchetMapKey(bobJid, id1)), false);
expect(map.containsKey(RatchetMapKey(bobJid, id2)), true);
final deviceMap = await aliceSession.getDeviceMap();
expect(deviceMap.containsKey(bobJid), true);
expect(deviceMap[bobJid], [id2]);
});
test('Test removing a ratchet when the user has only one', () async {
const aliceJid = 'alice@server.local';
const bobJid = 'bob@some.server.local';
final aliceSession = await OmemoSessionManager.generateNewIdentity(
aliceJid,
AlwaysTrustingTrustManager(),
opkAmount: 1,
);
final bobSession = await OmemoSessionManager.generateNewIdentity(
bobJid,
AlwaysTrustingTrustManager(),
opkAmount: 1,
);
// Alice sends a message to those two Bobs
await aliceSession.encryptToJid(
bobJid,
'Hallo Welt',
newSessions: [
await bobSession.getDeviceBundle(),
],
);
// One of those two sessions is broken, so Alice removes the session2 ratchet
final id = await bobSession.getDeviceId();
await aliceSession.removeRatchet(bobJid, id);
final map = aliceSession.getRatchetMap();
expect(map.containsKey(RatchetMapKey(bobJid, id)), false);
final deviceMap = await aliceSession.getDeviceMap();
expect(deviceMap.containsKey(bobJid), false);
});
});
test('Test acknowledging a ratchet', () async {
const aliceJid = 'alice@server.example';
const bobJid = 'bob@other.server.example';
// Alice and Bob generate their sessions
final aliceSession = await OmemoSessionManager.generateNewIdentity(
aliceJid,
AlwaysTrustingTrustManager(),
opkAmount: 1,
);
final bobSession = await OmemoSessionManager.generateNewIdentity(
bobJid,
AlwaysTrustingTrustManager(),
opkAmount: 1,
);
// Alice sends Bob a message
await aliceSession.encryptToJid(
bobJid,
'Hallo Welt',
newSessions: [
await bobSession.getDeviceBundle(),
],
);
expect(
await aliceSession.getUnacknowledgedRatchets(bobJid),
[
await bobSession.getDeviceId(),
],
);
// Bob sends alice an empty message
// ...
// Alice decrypts it
// ...
// Alice marks the ratchet as acknowledged
await aliceSession.ratchetAcknowledged(bobJid, await bobSession.getDeviceId());
expect(
(await aliceSession.getUnacknowledgedRatchets(bobJid))!.isEmpty,
true,
);
});
test('Test overwriting sessions', () async {
const aliceJid = 'alice@server.example';
const bobJid = 'bob@other.server.example';
// Alice and Bob generate their sessions
final aliceSession = await OmemoSessionManager.generateNewIdentity(
aliceJid,
AlwaysTrustingTrustManager(),
opkAmount: 1,
);
final bobSession = await OmemoSessionManager.generateNewIdentity(
bobJid,
AlwaysTrustingTrustManager(),
opkAmount: 2,
);
// Alice sends Bob a message
final msg1 = await aliceSession.encryptToJid(
bobJid,
'Hallo Welt',
newSessions: [
await bobSession.getDeviceBundle(),
],
);
await bobSession.decryptMessage(
msg1.ciphertext,
aliceJid,
await aliceSession.getDeviceId(),
msg1.encryptedKeys,
);
final aliceRatchet1 = aliceSession.getRatchet(
bobJid,
await bobSession.getDeviceId(),
);
final bobRatchet1 = bobSession.getRatchet(
aliceJid,
await aliceSession.getDeviceId(),
);
// Alice is impatient and immediately sends another message before the original one
// can be acknowledged by Bob
final msg2 = await aliceSession.encryptToJid(
bobJid,
"Why don't you answer?",
newSessions: [
await bobSession.getDeviceBundle(),
],
);
await bobSession.decryptMessage(
msg2.ciphertext,
aliceJid,
await aliceSession.getDeviceId(),
msg2.encryptedKeys,
);
final aliceRatchet2 = aliceSession.getRatchet(
bobJid,
await bobSession.getDeviceId(),
);
final bobRatchet2 = bobSession.getRatchet(
aliceJid,
await aliceSession.getDeviceId(),
);
// Both should only have one ratchet
expect(aliceSession.getRatchetMap().length, 1);
expect(bobSession.getRatchetMap().length, 1);
// The ratchets should both be different
expect(await aliceRatchet1.equals(aliceRatchet2), false);
expect(await bobRatchet1.equals(bobRatchet2), false);
});
}

View File

@@ -49,16 +49,16 @@ void main() {
bobJid,
'Hello Bob!',
newSessions: [
await (await bobSession.getDevice()).toBundle(),
await bobSession.getDeviceBundle(),
],
);
await bobSession.decryptMessage(
aliceMessage.ciphertext,
aliceJid,
(await aliceSession.getDevice()).id,
await aliceSession.getDeviceId(),
aliceMessage.encryptedKeys,
);
final aliceOld = aliceSession.getRatchet(bobJid, (await bobSession.getDevice()).id);
final aliceOld = aliceSession.getRatchet(bobJid, await bobSession.getDeviceId());
final aliceSerialised = await aliceOld.toJson();
final aliceNew = OmemoDoubleRatchet.fromJson(aliceSerialised);
@@ -79,8 +79,8 @@ void main() {
);
await oldSession.addSessionFromBundle(
'bob@localhost',
(await bobSession.getDevice()).id,
await (await bobSession.getDevice()).toBundle(),
await bobSession.getDeviceId(),
await bobSession.getDeviceBundle(),
);
// Serialise and deserialise
@@ -93,7 +93,7 @@ void main() {
final oldDevice = await oldSession.getDevice();
final newDevice = await newSession.getDevice();
expect(await oldDevice.equals(newDevice), true);
expect(oldSession.getDeviceMap(), newSession.getDeviceMap());
expect(await oldSession.getDeviceMap(), await newSession.getDeviceMap());
expect(oldSession.getRatchetMap().length, newSession.getRatchetMap().length);
for (final session in oldSession.getRatchetMap().entries) {