feat: Remove custom protobuf parsing
This commit is contained in:
parent
0ffc0b067a
commit
50f6513c6f
@ -1,8 +1,7 @@
|
|||||||
|
import 'package:omemo_dart/protobuf/schema.pb.dart';
|
||||||
import 'package:omemo_dart/src/crypto.dart';
|
import 'package:omemo_dart/src/crypto.dart';
|
||||||
import 'package:omemo_dart/src/errors.dart';
|
import 'package:omemo_dart/src/errors.dart';
|
||||||
import 'package:omemo_dart/src/helpers.dart';
|
import 'package:omemo_dart/src/helpers.dart';
|
||||||
import 'package:omemo_dart/src/protobuf/omemo_authenticated_message.dart';
|
|
||||||
import 'package:omemo_dart/src/protobuf/omemo_message.dart';
|
|
||||||
|
|
||||||
/// Info string for ENCRYPT
|
/// Info string for ENCRYPT
|
||||||
const encryptHkdfInfoString = 'OMEMO Message Key Material';
|
const encryptHkdfInfoString = 'OMEMO Message Key Material';
|
||||||
@ -22,12 +21,12 @@ Future<List<int>> encrypt(
|
|||||||
await aes256CbcEncrypt(plaintext, keys.encryptionKey, keys.iv);
|
await aes256CbcEncrypt(plaintext, keys.encryptionKey, keys.iv);
|
||||||
|
|
||||||
final header =
|
final header =
|
||||||
OmemoMessage.fromBuffer(associatedData.sublist(sessionAd.length))
|
OMEMOMessage.fromBuffer(associatedData.sublist(sessionAd.length))
|
||||||
..ciphertext = ciphertext;
|
..ciphertext = ciphertext;
|
||||||
final headerBytes = header.writeToBuffer();
|
final headerBytes = header.writeToBuffer();
|
||||||
final hmacInput = concat([sessionAd, headerBytes]);
|
final hmacInput = concat([sessionAd, headerBytes]);
|
||||||
final hmacResult = await truncatedHmac(hmacInput, keys.authenticationKey);
|
final hmacResult = await truncatedHmac(hmacInput, keys.authenticationKey);
|
||||||
final message = OmemoAuthenticatedMessage()
|
final message = OMEMOAuthenticatedMessage()
|
||||||
..mac = hmacResult
|
..mac = hmacResult
|
||||||
..message = headerBytes;
|
..message = headerBytes;
|
||||||
return message.writeToBuffer();
|
return message.writeToBuffer();
|
||||||
@ -46,15 +45,15 @@ Future<List<int>> decrypt(
|
|||||||
final keys = await deriveEncryptionKeys(mk, encryptHkdfInfoString);
|
final keys = await deriveEncryptionKeys(mk, encryptHkdfInfoString);
|
||||||
|
|
||||||
// Assumption ciphertext is a OMEMOAuthenticatedMessage
|
// Assumption ciphertext is a OMEMOAuthenticatedMessage
|
||||||
final message = OmemoAuthenticatedMessage.fromBuffer(ciphertext);
|
final message = OMEMOAuthenticatedMessage.fromBuffer(ciphertext);
|
||||||
final header = OmemoMessage.fromBuffer(message.message!);
|
final header = OMEMOMessage.fromBuffer(message.message);
|
||||||
|
|
||||||
final hmacInput = concat([sessionAd, header.writeToBuffer()]);
|
final hmacInput = concat([sessionAd, header.writeToBuffer()]);
|
||||||
final hmacResult = await truncatedHmac(hmacInput, keys.authenticationKey);
|
final hmacResult = await truncatedHmac(hmacInput, keys.authenticationKey);
|
||||||
|
|
||||||
if (!listsEqual(hmacResult, message.mac!)) {
|
if (!listsEqual(hmacResult, message.mac)) {
|
||||||
throw InvalidMessageHMACException();
|
throw InvalidMessageHMACException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return aes256CbcDecrypt(header.ciphertext!, keys.encryptionKey, keys.iv);
|
return aes256CbcDecrypt(header.ciphertext, keys.encryptionKey, keys.iv);
|
||||||
}
|
}
|
||||||
|
@ -2,20 +2,20 @@ import 'dart:convert';
|
|||||||
import 'package:cryptography/cryptography.dart';
|
import 'package:cryptography/cryptography.dart';
|
||||||
import 'package:hex/hex.dart';
|
import 'package:hex/hex.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:omemo_dart/protobuf/schema.pb.dart';
|
||||||
import 'package:omemo_dart/src/crypto.dart';
|
import 'package:omemo_dart/src/crypto.dart';
|
||||||
import 'package:omemo_dart/src/double_ratchet/crypto.dart';
|
import 'package:omemo_dart/src/double_ratchet/crypto.dart';
|
||||||
import 'package:omemo_dart/src/double_ratchet/kdf.dart';
|
import 'package:omemo_dart/src/double_ratchet/kdf.dart';
|
||||||
import 'package:omemo_dart/src/errors.dart';
|
import 'package:omemo_dart/src/errors.dart';
|
||||||
import 'package:omemo_dart/src/helpers.dart';
|
import 'package:omemo_dart/src/helpers.dart';
|
||||||
import 'package:omemo_dart/src/keys.dart';
|
import 'package:omemo_dart/src/keys.dart';
|
||||||
import 'package:omemo_dart/src/protobuf/omemo_message.dart';
|
|
||||||
|
|
||||||
/// Amount of messages we may skip per session
|
/// Amount of messages we may skip per session
|
||||||
const maxSkip = 1000;
|
const maxSkip = 1000;
|
||||||
|
|
||||||
class RatchetStep {
|
class RatchetStep {
|
||||||
const RatchetStep(this.header, this.ciphertext);
|
const RatchetStep(this.header, this.ciphertext);
|
||||||
final OmemoMessage header;
|
final OMEMOMessage header;
|
||||||
final List<int> ciphertext;
|
final List<int> ciphertext;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,12 +274,12 @@ class OmemoDoubleRatchet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<List<int>?> _trySkippedMessageKeys(
|
Future<List<int>?> _trySkippedMessageKeys(
|
||||||
OmemoMessage header,
|
OMEMOMessage header,
|
||||||
List<int> ciphertext,
|
List<int> ciphertext,
|
||||||
) async {
|
) async {
|
||||||
final key = SkippedKey(
|
final key = SkippedKey(
|
||||||
OmemoPublicKey.fromBytes(header.dhPub!, KeyPairType.x25519),
|
OmemoPublicKey.fromBytes(header.dhPub, KeyPairType.x25519),
|
||||||
header.n!,
|
header.n,
|
||||||
);
|
);
|
||||||
if (mkSkipped.containsKey(key)) {
|
if (mkSkipped.containsKey(key)) {
|
||||||
final mk = mkSkipped[key]!;
|
final mk = mkSkipped[key]!;
|
||||||
@ -312,11 +312,11 @@ class OmemoDoubleRatchet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _dhRatchet(OmemoMessage header) async {
|
Future<void> _dhRatchet(OMEMOMessage header) async {
|
||||||
pn = ns;
|
pn = ns;
|
||||||
ns = 0;
|
ns = 0;
|
||||||
nr = 0;
|
nr = 0;
|
||||||
dhr = OmemoPublicKey.fromBytes(header.dhPub!, KeyPairType.x25519);
|
dhr = OmemoPublicKey.fromBytes(header.dhPub, KeyPairType.x25519);
|
||||||
|
|
||||||
final newRk = await kdfRk(rk, await omemoDH(dhs, dhr!, 0));
|
final newRk = await kdfRk(rk, await omemoDH(dhs, dhr!, 0));
|
||||||
rk = List.from(newRk);
|
rk = List.from(newRk);
|
||||||
@ -333,7 +333,7 @@ class OmemoDoubleRatchet {
|
|||||||
final mk = await kdfCk(cks!, kdfCkNextMessageKey);
|
final mk = await kdfCk(cks!, kdfCkNextMessageKey);
|
||||||
|
|
||||||
cks = newCks;
|
cks = newCks;
|
||||||
final header = OmemoMessage()
|
final header = OMEMOMessage()
|
||||||
..dhPub = await dhs.pk.getBytes()
|
..dhPub = await dhs.pk.getBytes()
|
||||||
..pn = pn
|
..pn = pn
|
||||||
..n = ns;
|
..n = ns;
|
||||||
@ -356,7 +356,7 @@ class OmemoDoubleRatchet {
|
|||||||
///
|
///
|
||||||
/// Throws an SkippingTooManyMessagesException if too many messages were to be skipped.
|
/// Throws an SkippingTooManyMessagesException if too many messages were to be skipped.
|
||||||
Future<List<int>> ratchetDecrypt(
|
Future<List<int>> ratchetDecrypt(
|
||||||
OmemoMessage header,
|
OMEMOMessage header,
|
||||||
List<int> ciphertext,
|
List<int> ciphertext,
|
||||||
) async {
|
) async {
|
||||||
// Check if we skipped too many messages
|
// Check if we skipped too many messages
|
||||||
@ -366,15 +366,15 @@ class OmemoDoubleRatchet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final dhPubMatches = listsEqual(
|
final dhPubMatches = listsEqual(
|
||||||
header.dhPub!,
|
header.dhPub,
|
||||||
(await dhr?.getBytes()) ?? <int>[],
|
(await dhr?.getBytes()) ?? <int>[],
|
||||||
);
|
);
|
||||||
if (!dhPubMatches) {
|
if (!dhPubMatches) {
|
||||||
await _skipMessageKeys(header.pn!);
|
await _skipMessageKeys(header.pn);
|
||||||
await _dhRatchet(header);
|
await _dhRatchet(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _skipMessageKeys(header.n!);
|
await _skipMessageKeys(header.n);
|
||||||
final newCkr = await kdfCk(ckr!, kdfCkNextChainKey);
|
final newCkr = await kdfCk(ckr!, kdfCkNextChainKey);
|
||||||
final mk = await kdfCk(ckr!, kdfCkNextMessageKey);
|
final mk = await kdfCk(ckr!, kdfCkNextMessageKey);
|
||||||
ckr = newCkr;
|
ckr = newCkr;
|
||||||
|
@ -6,6 +6,7 @@ import 'package:cryptography/cryptography.dart';
|
|||||||
import 'package:hex/hex.dart';
|
import 'package:hex/hex.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:omemo_dart/protobuf/schema.pb.dart';
|
||||||
import 'package:omemo_dart/src/crypto.dart';
|
import 'package:omemo_dart/src/crypto.dart';
|
||||||
import 'package:omemo_dart/src/double_ratchet/double_ratchet.dart';
|
import 'package:omemo_dart/src/double_ratchet/double_ratchet.dart';
|
||||||
import 'package:omemo_dart/src/errors.dart';
|
import 'package:omemo_dart/src/errors.dart';
|
||||||
@ -21,9 +22,6 @@ import 'package:omemo_dart/src/omemo/events.dart';
|
|||||||
import 'package:omemo_dart/src/omemo/fingerprint.dart';
|
import 'package:omemo_dart/src/omemo/fingerprint.dart';
|
||||||
import 'package:omemo_dart/src/omemo/ratchet_map_key.dart';
|
import 'package:omemo_dart/src/omemo/ratchet_map_key.dart';
|
||||||
import 'package:omemo_dart/src/omemo/stanza.dart';
|
import 'package:omemo_dart/src/omemo/stanza.dart';
|
||||||
import 'package:omemo_dart/src/protobuf/omemo_authenticated_message.dart';
|
|
||||||
import 'package:omemo_dart/src/protobuf/omemo_key_exchange.dart';
|
|
||||||
import 'package:omemo_dart/src/protobuf/omemo_message.dart';
|
|
||||||
import 'package:omemo_dart/src/trust/base.dart';
|
import 'package:omemo_dart/src/trust/base.dart';
|
||||||
import 'package:omemo_dart/src/x3dh/x3dh.dart';
|
import 'package:omemo_dart/src/x3dh/x3dh.dart';
|
||||||
import 'package:synchronized/synchronized.dart';
|
import 'package:synchronized/synchronized.dart';
|
||||||
@ -194,7 +192,7 @@ class OmemoManager {
|
|||||||
Future<OmemoDoubleRatchet> _addSessionFromKeyExchange(
|
Future<OmemoDoubleRatchet> _addSessionFromKeyExchange(
|
||||||
String jid,
|
String jid,
|
||||||
int deviceId,
|
int deviceId,
|
||||||
OmemoKeyExchange kex,
|
OMEMOKeyExchange kex,
|
||||||
) async {
|
) async {
|
||||||
// Pick the correct SPK
|
// Pick the correct SPK
|
||||||
final device = await getDevice();
|
final device = await getDevice();
|
||||||
@ -209,17 +207,17 @@ class OmemoManager {
|
|||||||
|
|
||||||
final kexResult = await x3dhFromInitialMessage(
|
final kexResult = await x3dhFromInitialMessage(
|
||||||
X3DHMessage(
|
X3DHMessage(
|
||||||
OmemoPublicKey.fromBytes(kex.ik!, KeyPairType.ed25519),
|
OmemoPublicKey.fromBytes(kex.ik, KeyPairType.ed25519),
|
||||||
OmemoPublicKey.fromBytes(kex.ek!, KeyPairType.x25519),
|
OmemoPublicKey.fromBytes(kex.ek, KeyPairType.x25519),
|
||||||
kex.pkId!,
|
kex.pkId,
|
||||||
),
|
),
|
||||||
spk,
|
spk,
|
||||||
device.opks.values.elementAt(kex.pkId!),
|
device.opks.values.elementAt(kex.pkId),
|
||||||
device.ik,
|
device.ik,
|
||||||
);
|
);
|
||||||
final ratchet = await OmemoDoubleRatchet.acceptNewSession(
|
final ratchet = await OmemoDoubleRatchet.acceptNewSession(
|
||||||
spk,
|
spk,
|
||||||
OmemoPublicKey.fromBytes(kex.ik!, KeyPairType.ed25519),
|
OmemoPublicKey.fromBytes(kex.ik, KeyPairType.ed25519),
|
||||||
kexResult.sk,
|
kexResult.sk,
|
||||||
kexResult.ad,
|
kexResult.ad,
|
||||||
getTimestamp(),
|
getTimestamp(),
|
||||||
@ -234,7 +232,7 @@ class OmemoManager {
|
|||||||
/// Create a ratchet session initiated by Alice to the user with Jid [jid] and the device
|
/// Create a ratchet session initiated by Alice to the user with Jid [jid] and the device
|
||||||
/// [deviceId] from the bundle [bundle].
|
/// [deviceId] from the bundle [bundle].
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
Future<OmemoKeyExchange> addSessionFromBundle(
|
Future<OMEMOKeyExchange> addSessionFromBundle(
|
||||||
String jid,
|
String jid,
|
||||||
int deviceId,
|
int deviceId,
|
||||||
OmemoBundle bundle,
|
OmemoBundle bundle,
|
||||||
@ -255,7 +253,7 @@ class OmemoManager {
|
|||||||
await _trustManager.onNewSession(jid, deviceId);
|
await _trustManager.onNewSession(jid, deviceId);
|
||||||
_addSession(jid, deviceId, ratchet);
|
_addSession(jid, deviceId, ratchet);
|
||||||
|
|
||||||
return OmemoKeyExchange()
|
return OMEMOKeyExchange()
|
||||||
..pkId = kexResult.opkId
|
..pkId = kexResult.opkId
|
||||||
..spkId = bundle.spkId
|
..spkId = bundle.spkId
|
||||||
..ik = await device.ik.pk.getBytes()
|
..ik = await device.ik.pk.getBytes()
|
||||||
@ -312,17 +310,17 @@ class OmemoManager {
|
|||||||
|
|
||||||
final decodedRawKey = base64.decode(rawKey.value);
|
final decodedRawKey = base64.decode(rawKey.value);
|
||||||
List<int>? keyAndHmac;
|
List<int>? keyAndHmac;
|
||||||
OmemoAuthenticatedMessage authMessage;
|
OMEMOAuthenticatedMessage authMessage;
|
||||||
OmemoMessage? message;
|
OMEMOMessage? message;
|
||||||
|
|
||||||
// If the ratchet already existed, we store it. If it didn't, oldRatchet will stay
|
// If the ratchet already existed, we store it. If it didn't, oldRatchet will stay
|
||||||
// null.
|
// null.
|
||||||
final ratchetKey = RatchetMapKey(senderJid, senderDeviceId);
|
final ratchetKey = RatchetMapKey(senderJid, senderDeviceId);
|
||||||
final oldRatchet = getRatchet(ratchetKey)?.clone();
|
final oldRatchet = getRatchet(ratchetKey)?.clone();
|
||||||
if (rawKey.kex) {
|
if (rawKey.kex) {
|
||||||
final kex = OmemoKeyExchange.fromBuffer(decodedRawKey);
|
final kex = OMEMOKeyExchange.fromBuffer(decodedRawKey);
|
||||||
authMessage = kex.message!;
|
authMessage = kex.message;
|
||||||
message = OmemoMessage.fromBuffer(authMessage.message!);
|
message = OMEMOMessage.fromBuffer(authMessage.message);
|
||||||
|
|
||||||
// Guard against old key exchanges
|
// Guard against old key exchanges
|
||||||
if (oldRatchet != null) {
|
if (oldRatchet != null) {
|
||||||
@ -348,7 +346,7 @@ class OmemoManager {
|
|||||||
|
|
||||||
// Replace the OPK
|
// Replace the OPK
|
||||||
await _deviceLock.synchronized(() async {
|
await _deviceLock.synchronized(() async {
|
||||||
device = await device.replaceOnetimePrekey(kex.pkId!);
|
device = await device.replaceOnetimePrekey(kex.pkId);
|
||||||
|
|
||||||
// Commit the device
|
// Commit the device
|
||||||
_eventStreamController.add(DeviceModifiedEvent(device));
|
_eventStreamController.add(DeviceModifiedEvent(device));
|
||||||
@ -374,8 +372,8 @@ class OmemoManager {
|
|||||||
_log.finest('Kex failed due to $ex. Not proceeding with kex.');
|
_log.finest('Kex failed due to $ex. Not proceeding with kex.');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
authMessage = OmemoAuthenticatedMessage.fromBuffer(decodedRawKey);
|
authMessage = OMEMOAuthenticatedMessage.fromBuffer(decodedRawKey);
|
||||||
message = OmemoMessage.fromBuffer(authMessage.message!);
|
message = OMEMOMessage.fromBuffer(authMessage.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
final devices = _deviceList[senderJid];
|
final devices = _deviceList[senderJid];
|
||||||
@ -499,7 +497,7 @@ class OmemoManager {
|
|||||||
keyPayload = List<int>.filled(32, 0x0);
|
keyPayload = List<int>.filled(32, 0x0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final kex = <RatchetMapKey, OmemoKeyExchange>{};
|
final kex = <RatchetMapKey, OMEMOKeyExchange>{};
|
||||||
for (final jid in jids) {
|
for (final jid in jids) {
|
||||||
for (final newSession in await _fetchNewBundles(jid)) {
|
for (final newSession in await _fetchNewBundles(jid)) {
|
||||||
kex[RatchetMapKey(jid, newSession.id)] = await addSessionFromBundle(
|
kex[RatchetMapKey(jid, newSession.id)] = await addSessionFromBundle(
|
||||||
@ -551,7 +549,7 @@ class OmemoManager {
|
|||||||
if (kex.containsKey(ratchetKey)) {
|
if (kex.containsKey(ratchetKey)) {
|
||||||
// The ratchet did not exist
|
// The ratchet did not exist
|
||||||
final k = kex[ratchetKey]!
|
final k = kex[ratchetKey]!
|
||||||
..message = OmemoAuthenticatedMessage.fromBuffer(ciphertext);
|
..message = OMEMOAuthenticatedMessage.fromBuffer(ciphertext);
|
||||||
final buffer = base64.encode(k.writeToBuffer());
|
final buffer = base64.encode(k.writeToBuffer());
|
||||||
encryptedKeys.add(
|
encryptedKeys.add(
|
||||||
EncryptedKey(
|
EncryptedKey(
|
||||||
@ -568,8 +566,8 @@ class OmemoManager {
|
|||||||
// The ratchet exists but is not acked
|
// The ratchet exists but is not acked
|
||||||
if (ratchet.kex != null) {
|
if (ratchet.kex != null) {
|
||||||
final oldKex =
|
final oldKex =
|
||||||
OmemoKeyExchange.fromBuffer(base64.decode(ratchet.kex!))
|
OMEMOKeyExchange.fromBuffer(base64.decode(ratchet.kex!))
|
||||||
..message = OmemoAuthenticatedMessage.fromBuffer(ciphertext);
|
..message = OMEMOAuthenticatedMessage.fromBuffer(ciphertext);
|
||||||
|
|
||||||
encryptedKeys.add(
|
encryptedKeys.add(
|
||||||
EncryptedKey(
|
EncryptedKey(
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
import 'package:omemo_dart/src/helpers.dart';
|
|
||||||
import 'package:omemo_dart/src/protobuf/protobuf.dart';
|
|
||||||
|
|
||||||
class OmemoAuthenticatedMessage {
|
|
||||||
OmemoAuthenticatedMessage();
|
|
||||||
|
|
||||||
factory OmemoAuthenticatedMessage.fromBuffer(List<int> data) {
|
|
||||||
var i = 0;
|
|
||||||
|
|
||||||
// required bytes mac = 1;
|
|
||||||
if (data[0] != fieldId(1, fieldTypeByteArray)) {
|
|
||||||
throw Exception();
|
|
||||||
}
|
|
||||||
final mac = data.sublist(2, i + 2 + data[1]);
|
|
||||||
i += data[1] + 2;
|
|
||||||
|
|
||||||
if (data[i] != fieldId(2, fieldTypeByteArray)) {
|
|
||||||
throw Exception();
|
|
||||||
}
|
|
||||||
final message = data.sublist(i + 2, i + 2 + data[i + 1]);
|
|
||||||
|
|
||||||
return OmemoAuthenticatedMessage()
|
|
||||||
..mac = mac
|
|
||||||
..message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<int>? mac;
|
|
||||||
List<int>? message;
|
|
||||||
|
|
||||||
List<int> writeToBuffer() {
|
|
||||||
return concat([
|
|
||||||
[fieldId(1, fieldTypeByteArray), mac!.length],
|
|
||||||
mac!,
|
|
||||||
[fieldId(2, fieldTypeByteArray), message!.length],
|
|
||||||
message!,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
import 'package:omemo_dart/src/helpers.dart';
|
|
||||||
import 'package:omemo_dart/src/protobuf/omemo_authenticated_message.dart';
|
|
||||||
import 'package:omemo_dart/src/protobuf/protobuf.dart';
|
|
||||||
|
|
||||||
class OmemoKeyExchange {
|
|
||||||
OmemoKeyExchange();
|
|
||||||
|
|
||||||
factory OmemoKeyExchange.fromBuffer(List<int> data) {
|
|
||||||
var i = 0;
|
|
||||||
|
|
||||||
if (data[i] != fieldId(1, fieldTypeUint32)) {
|
|
||||||
throw Exception();
|
|
||||||
}
|
|
||||||
var decoded = decodeVarint(data, 1);
|
|
||||||
final pkId = decoded.n;
|
|
||||||
i += decoded.length + 1;
|
|
||||||
|
|
||||||
if (data[i] != fieldId(2, fieldTypeUint32)) {
|
|
||||||
throw Exception();
|
|
||||||
}
|
|
||||||
decoded = decodeVarint(data, i + 1);
|
|
||||||
final spkId = decoded.n;
|
|
||||||
i += decoded.length + 1;
|
|
||||||
|
|
||||||
if (data[i] != fieldId(3, fieldTypeByteArray)) {
|
|
||||||
throw Exception();
|
|
||||||
}
|
|
||||||
final ik = data.sublist(i + 2, i + 2 + data[i + 1]);
|
|
||||||
i += 2 + data[i + 1];
|
|
||||||
|
|
||||||
if (data[i] != fieldId(4, fieldTypeByteArray)) {
|
|
||||||
throw Exception();
|
|
||||||
}
|
|
||||||
final ek = data.sublist(i + 2, i + 2 + data[i + 1]);
|
|
||||||
i += 2 + data[i + 1];
|
|
||||||
|
|
||||||
if (data[i] != fieldId(5, fieldTypeByteArray)) {
|
|
||||||
throw Exception();
|
|
||||||
}
|
|
||||||
final message = OmemoAuthenticatedMessage.fromBuffer(data.sublist(i + 2));
|
|
||||||
|
|
||||||
return OmemoKeyExchange()
|
|
||||||
..pkId = pkId
|
|
||||||
..spkId = spkId
|
|
||||||
..ik = ik
|
|
||||||
..ek = ek
|
|
||||||
..message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
int? pkId;
|
|
||||||
int? spkId;
|
|
||||||
List<int>? ik;
|
|
||||||
List<int>? ek;
|
|
||||||
OmemoAuthenticatedMessage? message;
|
|
||||||
|
|
||||||
List<int> writeToBuffer() {
|
|
||||||
final msg = message!.writeToBuffer();
|
|
||||||
return concat([
|
|
||||||
[fieldId(1, fieldTypeUint32)],
|
|
||||||
encodeVarint(pkId!),
|
|
||||||
[fieldId(2, fieldTypeUint32)],
|
|
||||||
encodeVarint(spkId!),
|
|
||||||
[fieldId(3, fieldTypeByteArray), ik!.length],
|
|
||||||
ik!,
|
|
||||||
[fieldId(4, fieldTypeByteArray), ek!.length],
|
|
||||||
ek!,
|
|
||||||
[fieldId(5, fieldTypeByteArray), msg.length],
|
|
||||||
msg,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
import 'package:omemo_dart/src/helpers.dart';
|
|
||||||
import 'package:omemo_dart/src/protobuf/protobuf.dart';
|
|
||||||
|
|
||||||
class OmemoMessage {
|
|
||||||
OmemoMessage();
|
|
||||||
|
|
||||||
factory OmemoMessage.fromBuffer(List<int> data) {
|
|
||||||
var i = 0;
|
|
||||||
|
|
||||||
// required uint32 n = 1;
|
|
||||||
if (data[0] != fieldId(1, fieldTypeUint32)) {
|
|
||||||
throw Exception();
|
|
||||||
}
|
|
||||||
var decode = decodeVarint(data, 1);
|
|
||||||
final n = decode.n;
|
|
||||||
i += decode.length + 1;
|
|
||||||
|
|
||||||
// required uint32 pn = 2;
|
|
||||||
if (data[i] != fieldId(2, fieldTypeUint32)) {
|
|
||||||
throw Exception();
|
|
||||||
}
|
|
||||||
decode = decodeVarint(data, i + 1);
|
|
||||||
final pn = decode.n;
|
|
||||||
i += decode.length + 1;
|
|
||||||
|
|
||||||
// required bytes dh_pub = 3;
|
|
||||||
if (data[i] != fieldId(3, fieldTypeByteArray)) {
|
|
||||||
throw Exception();
|
|
||||||
}
|
|
||||||
final dhPub = data.sublist(i + 2, i + 2 + data[i + 1]);
|
|
||||||
i += 2 + data[i + 1];
|
|
||||||
|
|
||||||
// optional bytes ciphertext = 4;
|
|
||||||
List<int>? ciphertext;
|
|
||||||
if (i < data.length) {
|
|
||||||
if (data[i] != fieldId(4, fieldTypeByteArray)) {
|
|
||||||
throw Exception();
|
|
||||||
}
|
|
||||||
|
|
||||||
ciphertext = data.sublist(i + 2, i + 2 + data[i + 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return OmemoMessage()
|
|
||||||
..n = n
|
|
||||||
..pn = pn
|
|
||||||
..dhPub = dhPub
|
|
||||||
..ciphertext = ciphertext;
|
|
||||||
}
|
|
||||||
|
|
||||||
int? n;
|
|
||||||
int? pn;
|
|
||||||
List<int>? dhPub;
|
|
||||||
List<int>? ciphertext;
|
|
||||||
|
|
||||||
List<int> writeToBuffer() {
|
|
||||||
final data = concat([
|
|
||||||
[fieldId(1, fieldTypeUint32)],
|
|
||||||
encodeVarint(n!),
|
|
||||||
[fieldId(2, fieldTypeUint32)],
|
|
||||||
encodeVarint(pn!),
|
|
||||||
[fieldId(3, fieldTypeByteArray), dhPub!.length],
|
|
||||||
dhPub!,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (ciphertext != null) {
|
|
||||||
return concat([
|
|
||||||
data,
|
|
||||||
[fieldId(4, fieldTypeByteArray), ciphertext!.length],
|
|
||||||
ciphertext!,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
/// Masks the 7 LSB
|
|
||||||
const lsb7Mask = 0x7F;
|
|
||||||
|
|
||||||
/// Constant for setting the MSB
|
|
||||||
const msb = 1 << 7;
|
|
||||||
|
|
||||||
/// Field types
|
|
||||||
const fieldTypeUint32 = 0;
|
|
||||||
const fieldTypeByteArray = 2;
|
|
||||||
|
|
||||||
int fieldId(int number, int type) {
|
|
||||||
return (number << 3) | type;
|
|
||||||
}
|
|
||||||
|
|
||||||
class VarintDecode {
|
|
||||||
const VarintDecode(this.n, this.length);
|
|
||||||
final int n;
|
|
||||||
final int length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decode a Varint that begins at [input]'s index [offset].
|
|
||||||
VarintDecode decodeVarint(List<int> input, int offset) {
|
|
||||||
// The return value
|
|
||||||
var n = 0;
|
|
||||||
// The byte offset counter
|
|
||||||
var i = 0;
|
|
||||||
|
|
||||||
// Iterate until the MSB of the byte is 0
|
|
||||||
while (true) {
|
|
||||||
// Mask only the 7 LSB and "move" them accordingly
|
|
||||||
n += (input[offset + i] & lsb7Mask) << (7 * i);
|
|
||||||
|
|
||||||
// Break if we reached the end
|
|
||||||
if (input[offset + i] & 1 << 7 == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return VarintDecode(n, i + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encodes the integer [i] into a Varint.
|
|
||||||
List<int> encodeVarint(int i) {
|
|
||||||
assert(i >= 0, "Two's complement is not implemented");
|
|
||||||
final ret = List<int>.empty(growable: true);
|
|
||||||
|
|
||||||
// Thanks to https://github.com/hathibelagal-dev/LEB128 for the trick with toRadixString!
|
|
||||||
final numSevenBlocks = (i.toRadixString(2).length / 7).ceil();
|
|
||||||
for (var j = 0; j < numSevenBlocks; j++) {
|
|
||||||
// The 7 LSB of the byte we're creating
|
|
||||||
final x = (i & (lsb7Mask << j * 7)) >> j * 7;
|
|
||||||
|
|
||||||
if (j == numSevenBlocks - 1) {
|
|
||||||
// If we were to shift further, we only get zero, so we're at the end
|
|
||||||
ret.add(x);
|
|
||||||
} else {
|
|
||||||
// We still have at least one bit more to go, so set the MSB to 1
|
|
||||||
ret.add(x + msb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
@ -14,11 +14,11 @@ dependencies:
|
|||||||
logging: ^1.0.2
|
logging: ^1.0.2
|
||||||
meta: ^1.7.0
|
meta: ^1.7.0
|
||||||
pinenacl: ^0.5.1
|
pinenacl: ^0.5.1
|
||||||
|
protobuf: ^2.1.0
|
||||||
|
protoc_plugin: ^20.0.1
|
||||||
synchronized: ^3.0.0+2
|
synchronized: ^3.0.0+2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
lints: ^2.0.0
|
lints: ^2.0.0
|
||||||
protobuf: ^2.1.0
|
|
||||||
protoc_plugin: ^20.0.1
|
|
||||||
test: ^1.21.0
|
test: ^1.21.0
|
||||||
very_good_analysis: ^3.0.1
|
very_good_analysis: ^3.0.1
|
||||||
|
@ -1,956 +0,0 @@
|
|||||||
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 replacing a onetime prekey', () async {
|
|
||||||
const aliceJid = 'alice@server.example';
|
|
||||||
final device = await OmemoDevice.generateNewDevice(aliceJid, opkAmount: 1);
|
|
||||||
|
|
||||||
final newDevice = await device.replaceOnetimePrekey(0);
|
|
||||||
|
|
||||||
expect(device.jid, newDevice.jid);
|
|
||||||
expect(device.id, newDevice.id);
|
|
||||||
|
|
||||||
var opksMatch = true;
|
|
||||||
if (newDevice.opks.length != device.opks.length) {
|
|
||||||
opksMatch = false;
|
|
||||||
} else {
|
|
||||||
for (final entry in device.opks.entries) {
|
|
||||||
final m = await newDevice.opks[entry.key]?.equals(entry.value) ?? false;
|
|
||||||
if (!m) opksMatch = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(opksMatch, true);
|
|
||||||
expect(await device.ik.equals(newDevice.ik), true);
|
|
||||||
expect(await device.spk.equals(newDevice.spk), true);
|
|
||||||
|
|
||||||
final oldSpkMatch = device.oldSpk != null
|
|
||||||
? await device.oldSpk!.equals(newDevice.oldSpk!)
|
|
||||||
: newDevice.oldSpk == null;
|
|
||||||
expect(oldSpkMatch, true);
|
|
||||||
expect(listsEqual(device.spkSignature, newDevice.spkSignature), true);
|
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
|
||||||
var deviceMapModified = 0;
|
|
||||||
final aliceSession = await OmemoSessionManager.generateNewIdentity(
|
|
||||||
aliceJid,
|
|
||||||
AlwaysTrustingTrustManager(),
|
|
||||||
opkAmount: 1,
|
|
||||||
);
|
|
||||||
final bobSession = await OmemoSessionManager.generateNewIdentity(
|
|
||||||
bobJid,
|
|
||||||
AlwaysTrustingTrustManager(),
|
|
||||||
opkAmount: 1,
|
|
||||||
);
|
|
||||||
final bobOpks = (await bobSession.getDevice()).opks.values.toList();
|
|
||||||
bobSession.eventStream.listen((event) {
|
|
||||||
if (event is DeviceModifiedEvent) {
|
|
||||||
deviceModified = true;
|
|
||||||
} else if (event is RatchetModifiedEvent) {
|
|
||||||
ratchetModified++;
|
|
||||||
} else if (event is DeviceListModifiedEvent) {
|
|
||||||
deviceMapModified++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Alice encrypts a message for Bob
|
|
||||||
const messagePlaintext = 'Hello Bob!';
|
|
||||||
final aliceMessage = await aliceSession.encryptToJid(
|
|
||||||
bobJid,
|
|
||||||
messagePlaintext,
|
|
||||||
newSessions: [
|
|
||||||
await bobSession.getDeviceBundle(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
expect(aliceMessage.encryptedKeys.length, 1);
|
|
||||||
|
|
||||||
// Alice sends the message to Bob
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// Bob decrypts it
|
|
||||||
final bobMessage = await bobSession.decryptMessage(
|
|
||||||
aliceMessage.ciphertext,
|
|
||||||
aliceJid,
|
|
||||||
await aliceSession.getDeviceId(),
|
|
||||||
aliceMessage.encryptedKeys,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
expect(messagePlaintext, bobMessage);
|
|
||||||
// The ratchet should be modified two times: Once for when the ratchet is created and
|
|
||||||
// other time for when the message is decrypted
|
|
||||||
expect(ratchetModified, 2);
|
|
||||||
// Bob's device map should be modified once
|
|
||||||
expect(deviceMapModified, 1);
|
|
||||||
// The event should be triggered
|
|
||||||
expect(deviceModified, true);
|
|
||||||
// Bob should have replaced his OPK
|
|
||||||
expect(
|
|
||||||
listsEqual(bobOpks, (await bobSession.getDevice()).opks.values.toList()),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ratchets are acked
|
|
||||||
await aliceSession.ratchetAcknowledged(
|
|
||||||
bobJid, await bobSession.getDeviceId());
|
|
||||||
await bobSession.ratchetAcknowledged(
|
|
||||||
aliceJid, await aliceSession.getDeviceId());
|
|
||||||
|
|
||||||
// Bob responds to Alice
|
|
||||||
const bobResponseText = 'Oh, hello Alice!';
|
|
||||||
final bobResponseMessage = await bobSession.encryptToJid(
|
|
||||||
aliceJid,
|
|
||||||
bobResponseText,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Bob sends the message to Alice
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// Alice decrypts it
|
|
||||||
final aliceReceivedMessage = await aliceSession.decryptMessage(
|
|
||||||
bobResponseMessage.ciphertext,
|
|
||||||
bobJid,
|
|
||||||
await bobSession.getDeviceId(),
|
|
||||||
bobResponseMessage.encryptedKeys,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
expect(bobResponseText, aliceReceivedMessage);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test using OMEMO sessions with only two devices for the receiver',
|
|
||||||
() 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,
|
|
||||||
);
|
|
||||||
// Bob's other device
|
|
||||||
final bobSession2 = await OmemoSessionManager.generateNewIdentity(
|
|
||||||
bobJid,
|
|
||||||
AlwaysTrustingTrustManager(),
|
|
||||||
opkAmount: 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Alice encrypts a message for Bob
|
|
||||||
const messagePlaintext = 'Hello Bob!';
|
|
||||||
final aliceMessage = await aliceSession.encryptToJid(
|
|
||||||
bobJid,
|
|
||||||
messagePlaintext,
|
|
||||||
newSessions: [
|
|
||||||
await bobSession.getDeviceBundle(),
|
|
||||||
await bobSession2.getDeviceBundle(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
expect(aliceMessage.encryptedKeys.length, 2);
|
|
||||||
expect(aliceMessage.encryptedKeys[0].kex, true);
|
|
||||||
expect(aliceMessage.encryptedKeys[1].kex, true);
|
|
||||||
|
|
||||||
// Alice sends the message to Bob
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// Bob decrypts it
|
|
||||||
final bobMessage = await bobSession.decryptMessage(
|
|
||||||
aliceMessage.ciphertext,
|
|
||||||
aliceJid,
|
|
||||||
await aliceSession.getDeviceId(),
|
|
||||||
aliceMessage.encryptedKeys,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
expect(messagePlaintext, bobMessage);
|
|
||||||
|
|
||||||
// Ratchets are acked
|
|
||||||
await aliceSession.ratchetAcknowledged(
|
|
||||||
bobJid, await bobSession.getDeviceId());
|
|
||||||
await bobSession.ratchetAcknowledged(
|
|
||||||
aliceJid, await aliceSession.getDeviceId());
|
|
||||||
|
|
||||||
// Bob responds to Alice
|
|
||||||
const bobResponseText = 'Oh, hello Alice!';
|
|
||||||
final bobResponseMessage = await bobSession.encryptToJid(
|
|
||||||
aliceJid,
|
|
||||||
bobResponseText,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Bob sends the message to Alice
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// Alice decrypts it
|
|
||||||
final aliceReceivedMessage = await aliceSession.decryptMessage(
|
|
||||||
bobResponseMessage.ciphertext,
|
|
||||||
bobJid,
|
|
||||||
await bobSession.getDeviceId(),
|
|
||||||
bobResponseMessage.encryptedKeys,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
expect(bobResponseText, aliceReceivedMessage);
|
|
||||||
|
|
||||||
// Alice checks the fingerprints
|
|
||||||
final fingerprints = await aliceSession.getHexFingerprintsForJid(bobJid);
|
|
||||||
// Check that they the fingerprints are correct
|
|
||||||
expect(fingerprints.length, 2);
|
|
||||||
expect(fingerprints[0] != fingerprints[1], true);
|
|
||||||
// Check that those two calls do not throw an exception
|
|
||||||
aliceSession
|
|
||||||
..getRatchet(bobJid, fingerprints[0].deviceId)
|
|
||||||
..getRatchet(bobJid, fingerprints[1].deviceId);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test using OMEMO sessions with encrypt to self', () async {
|
|
||||||
const aliceJid = 'alice@server.example';
|
|
||||||
const bobJid = 'bob@other.server.example';
|
|
||||||
|
|
||||||
// Alice and Bob generate their sessions
|
|
||||||
final aliceSession1 = await OmemoSessionManager.generateNewIdentity(
|
|
||||||
aliceJid,
|
|
||||||
AlwaysTrustingTrustManager(),
|
|
||||||
opkAmount: 1,
|
|
||||||
);
|
|
||||||
final aliceSession2 = await OmemoSessionManager.generateNewIdentity(
|
|
||||||
aliceJid,
|
|
||||||
AlwaysTrustingTrustManager(),
|
|
||||||
opkAmount: 1,
|
|
||||||
);
|
|
||||||
final bobSession = await OmemoSessionManager.generateNewIdentity(
|
|
||||||
bobJid,
|
|
||||||
AlwaysTrustingTrustManager(),
|
|
||||||
opkAmount: 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Alice encrypts a message for Bob
|
|
||||||
const messagePlaintext = 'Hello Bob!';
|
|
||||||
final aliceMessage = await aliceSession1.encryptToJids(
|
|
||||||
[bobJid, aliceJid],
|
|
||||||
messagePlaintext,
|
|
||||||
newSessions: [
|
|
||||||
await bobSession.getDeviceBundle(),
|
|
||||||
await aliceSession2.getDeviceBundle(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
expect(aliceMessage.encryptedKeys.length, 2);
|
|
||||||
|
|
||||||
// Alice sends the message to Bob
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// Bob decrypts it
|
|
||||||
final bobMessage = await bobSession.decryptMessage(
|
|
||||||
aliceMessage.ciphertext,
|
|
||||||
aliceJid,
|
|
||||||
await aliceSession1.getDeviceId(),
|
|
||||||
aliceMessage.encryptedKeys,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
expect(messagePlaintext, bobMessage);
|
|
||||||
|
|
||||||
// Alice's other device decrypts it
|
|
||||||
final aliceMessage2 = await aliceSession2.decryptMessage(
|
|
||||||
aliceMessage.ciphertext,
|
|
||||||
aliceJid,
|
|
||||||
await aliceSession1.getDeviceId(),
|
|
||||||
aliceMessage.encryptedKeys,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
expect(messagePlaintext, aliceMessage2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test sending empty OMEMO messages', () 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,
|
|
||||||
null,
|
|
||||||
newSessions: [
|
|
||||||
await bobSession.getDeviceBundle(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
expect(aliceMessage.encryptedKeys.length, 1);
|
|
||||||
expect(aliceMessage.ciphertext, null);
|
|
||||||
|
|
||||||
// Alice sends the message to Bob
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// Bob decrypts it
|
|
||||||
final bobMessage = await bobSession.decryptMessage(
|
|
||||||
aliceMessage.ciphertext,
|
|
||||||
aliceJid,
|
|
||||||
await aliceSession.getDeviceId(),
|
|
||||||
aliceMessage.encryptedKeys,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
expect(bobMessage, null);
|
|
||||||
|
|
||||||
// This call must not cause an exception
|
|
||||||
bobSession.getRatchet(aliceJid, await aliceSession.getDeviceId());
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test rotating the Signed Prekey', () async {
|
|
||||||
// Generate the session
|
|
||||||
const aliceJid = 'alice@some.server';
|
|
||||||
final aliceSession = await OmemoSessionManager.generateNewIdentity(
|
|
||||||
aliceJid,
|
|
||||||
AlwaysTrustingTrustManager(),
|
|
||||||
opkAmount: 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Setup an event listener
|
|
||||||
final oldDevice = await aliceSession.getDevice();
|
|
||||||
OmemoDevice? newDevice;
|
|
||||||
aliceSession.eventStream.listen((event) {
|
|
||||||
if (event is DeviceModifiedEvent) {
|
|
||||||
newDevice = event.device;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Rotate the Signed Prekey
|
|
||||||
await aliceSession.rotateSignedPrekey();
|
|
||||||
|
|
||||||
// Just for safety...
|
|
||||||
await Future<void>.delayed(const Duration(seconds: 2));
|
|
||||||
|
|
||||||
expect(await oldDevice.equals(newDevice!), false);
|
|
||||||
expect(await newDevice!.equals(await aliceSession.getDevice()), true);
|
|
||||||
|
|
||||||
expect(await newDevice!.oldSpk!.equals(oldDevice.spk), true);
|
|
||||||
expect(newDevice!.oldSpkId, oldDevice.spkId);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test accepting a session with an old SPK', () 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
|
|
||||||
const messagePlaintext = 'Hello Bob!';
|
|
||||||
final aliceMessage = await aliceSession.encryptToJid(
|
|
||||||
bobJid,
|
|
||||||
messagePlaintext,
|
|
||||||
newSessions: [
|
|
||||||
await bobSession.getDeviceBundle(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
expect(aliceMessage.encryptedKeys.length, 1);
|
|
||||||
|
|
||||||
// Alice loses her Internet connection. Bob rotates his SPK.
|
|
||||||
await bobSession.rotateSignedPrekey();
|
|
||||||
|
|
||||||
// Alice regains her Internet connection and sends the message to Bob
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// Bob decrypts it
|
|
||||||
final bobMessage = await bobSession.decryptMessage(
|
|
||||||
aliceMessage.ciphertext,
|
|
||||||
aliceJid,
|
|
||||||
await aliceSession.getDeviceId(),
|
|
||||||
aliceMessage.encryptedKeys,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
expect(messagePlaintext, bobMessage);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test trust bypassing with empty OMEMO messages', () async {
|
|
||||||
const aliceJid = 'alice@server.example';
|
|
||||||
const bobJid = 'bob@other.server.example';
|
|
||||||
|
|
||||||
// Alice and Bob generate their sessions
|
|
||||||
final aliceSession = await OmemoSessionManager.generateNewIdentity(
|
|
||||||
aliceJid,
|
|
||||||
NeverTrustingTrustManager(),
|
|
||||||
opkAmount: 1,
|
|
||||||
);
|
|
||||||
final bobSession = await OmemoSessionManager.generateNewIdentity(
|
|
||||||
bobJid,
|
|
||||||
NeverTrustingTrustManager(),
|
|
||||||
opkAmount: 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Alice encrypts an empty message for Bob
|
|
||||||
final aliceMessage = await aliceSession.encryptToJid(
|
|
||||||
bobJid,
|
|
||||||
null,
|
|
||||||
newSessions: [
|
|
||||||
await bobSession.getDeviceBundle(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Despite Alice not trusting Bob's device, we should have encrypted it for his
|
|
||||||
// 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,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ratchets are acked
|
|
||||||
await aliceSession.ratchetAcknowledged(
|
|
||||||
bobJid, await bobSession.getDeviceId());
|
|
||||||
await bobSession.ratchetAcknowledged(
|
|
||||||
aliceJid, await aliceSession.getDeviceId());
|
|
||||||
|
|
||||||
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,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
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,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
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,
|
|
||||||
getTimestamp(),
|
|
||||||
);
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test resending key exchanges', () 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(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
// The first message should be a kex message
|
|
||||||
expect(msg1.encryptedKeys.first.kex, true);
|
|
||||||
|
|
||||||
await bobSession.decryptMessage(
|
|
||||||
msg1.ciphertext,
|
|
||||||
aliceJid,
|
|
||||||
await aliceSession.getDeviceId(),
|
|
||||||
msg1.encryptedKeys,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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?",
|
|
||||||
);
|
|
||||||
expect(msg2.encryptedKeys.first.kex, true);
|
|
||||||
|
|
||||||
await bobSession.decryptMessage(
|
|
||||||
msg2.ciphertext,
|
|
||||||
aliceJid,
|
|
||||||
await aliceSession.getDeviceId(),
|
|
||||||
msg2.encryptedKeys,
|
|
||||||
getTimestamp(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test receiving old messages including a KEX', () 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,
|
|
||||||
);
|
|
||||||
|
|
||||||
final bobsReceivedMessages = List<EncryptionResult>.empty(growable: true);
|
|
||||||
final bobsReceivedMessagesTimestamps = List<int>.empty(growable: true);
|
|
||||||
|
|
||||||
// Alice sends Bob a message
|
|
||||||
final msg1 = await aliceSession.encryptToJid(
|
|
||||||
bobJid,
|
|
||||||
'Hallo Welt',
|
|
||||||
newSessions: [
|
|
||||||
await bobSession.getDeviceBundle(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
bobsReceivedMessages.add(msg1);
|
|
||||||
final t1 = getTimestamp();
|
|
||||||
bobsReceivedMessagesTimestamps.add(t1);
|
|
||||||
|
|
||||||
await bobSession.decryptMessage(
|
|
||||||
msg1.ciphertext,
|
|
||||||
aliceJid,
|
|
||||||
await aliceSession.getDeviceId(),
|
|
||||||
msg1.encryptedKeys,
|
|
||||||
t1,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ratchets are acked
|
|
||||||
await aliceSession.ratchetAcknowledged(
|
|
||||||
bobJid, await bobSession.getDeviceId());
|
|
||||||
await bobSession.ratchetAcknowledged(
|
|
||||||
aliceJid, await aliceSession.getDeviceId());
|
|
||||||
|
|
||||||
// Bob responds
|
|
||||||
final msg2 = await bobSession.encryptToJid(
|
|
||||||
aliceJid,
|
|
||||||
'Hello!',
|
|
||||||
);
|
|
||||||
|
|
||||||
await aliceSession.decryptMessage(
|
|
||||||
msg2.ciphertext,
|
|
||||||
bobJid,
|
|
||||||
await bobSession.getDeviceId(),
|
|
||||||
msg2.encryptedKeys,
|
|
||||||
getTimestamp(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Send some messages between the two
|
|
||||||
for (var i = 0; i < 100; i++) {
|
|
||||||
final msg = await aliceSession.encryptToJid(
|
|
||||||
bobJid,
|
|
||||||
'Hello $i',
|
|
||||||
);
|
|
||||||
bobsReceivedMessages.add(msg);
|
|
||||||
final t = getTimestamp();
|
|
||||||
bobsReceivedMessagesTimestamps.add(t);
|
|
||||||
final result = await bobSession.decryptMessage(
|
|
||||||
msg.ciphertext,
|
|
||||||
aliceJid,
|
|
||||||
await aliceSession.getDeviceId(),
|
|
||||||
msg.encryptedKeys,
|
|
||||||
t,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result, 'Hello $i');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Due to some issue with the transport protocol, the messages to Bob are received
|
|
||||||
// again.
|
|
||||||
final ratchetPreError = bobSession
|
|
||||||
.getRatchet(aliceJid, await aliceSession.getDeviceId())
|
|
||||||
.clone();
|
|
||||||
var invalidKex = 0;
|
|
||||||
var errorCounter = 0;
|
|
||||||
for (var i = 0; i < bobsReceivedMessages.length; i++) {
|
|
||||||
final msg = bobsReceivedMessages[i];
|
|
||||||
try {
|
|
||||||
await bobSession.decryptMessage(
|
|
||||||
msg.ciphertext,
|
|
||||||
aliceJid,
|
|
||||||
await aliceSession.getDeviceId(),
|
|
||||||
msg.encryptedKeys,
|
|
||||||
bobsReceivedMessagesTimestamps[i],
|
|
||||||
);
|
|
||||||
expect(true, false);
|
|
||||||
} on InvalidMessageHMACException catch (_) {
|
|
||||||
errorCounter++;
|
|
||||||
} on InvalidKeyExchangeException catch (_) {
|
|
||||||
invalidKex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final ratchetPostError = bobSession
|
|
||||||
.getRatchet(aliceJid, await aliceSession.getDeviceId())
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
// The 100 messages including the initial KEX message
|
|
||||||
expect(invalidKex, 1);
|
|
||||||
expect(errorCounter, 100);
|
|
||||||
expect(await ratchetPreError.equals(ratchetPostError), true);
|
|
||||||
|
|
||||||
final msg3 = await aliceSession.encryptToJid(
|
|
||||||
bobJid,
|
|
||||||
'Are you okay?',
|
|
||||||
);
|
|
||||||
final result = await bobSession.decryptMessage(
|
|
||||||
msg3.ciphertext,
|
|
||||||
aliceJid,
|
|
||||||
await aliceSession.getDeviceId(),
|
|
||||||
msg3.encryptedKeys,
|
|
||||||
104,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result, 'Are you okay?');
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Test ignoring a new KEX when we haven't acket it yet", () 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
|
|
||||||
final msg1 = await aliceSession.encryptToJid(
|
|
||||||
bobJid,
|
|
||||||
'Hallo Welt',
|
|
||||||
newSessions: [
|
|
||||||
await bobSession.getDeviceBundle(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
expect(msg1.encryptedKeys.first.kex, true);
|
|
||||||
|
|
||||||
await bobSession.decryptMessage(
|
|
||||||
msg1.ciphertext,
|
|
||||||
aliceJid,
|
|
||||||
await aliceSession.getDeviceId(),
|
|
||||||
msg1.encryptedKeys,
|
|
||||||
getTimestamp(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Alice sends another message before the ack can reach us
|
|
||||||
final msg2 = await aliceSession.encryptToJid(
|
|
||||||
bobJid,
|
|
||||||
'ANSWER ME!',
|
|
||||||
);
|
|
||||||
expect(msg2.encryptedKeys.first.kex, true);
|
|
||||||
|
|
||||||
await bobSession.decryptMessage(
|
|
||||||
msg2.ciphertext,
|
|
||||||
aliceJid,
|
|
||||||
await aliceSession.getDeviceId(),
|
|
||||||
msg2.encryptedKeys,
|
|
||||||
getTimestamp(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Now the acks reach us
|
|
||||||
await aliceSession.ratchetAcknowledged(
|
|
||||||
bobJid, await bobSession.getDeviceId());
|
|
||||||
await bobSession.ratchetAcknowledged(
|
|
||||||
aliceJid, await aliceSession.getDeviceId());
|
|
||||||
|
|
||||||
// Alice sends another message
|
|
||||||
final msg3 = await aliceSession.encryptToJid(
|
|
||||||
bobJid,
|
|
||||||
"You read the message, didn't you?",
|
|
||||||
);
|
|
||||||
expect(msg3.encryptedKeys.first.kex, false);
|
|
||||||
|
|
||||||
await bobSession.decryptMessage(
|
|
||||||
msg3.ciphertext,
|
|
||||||
aliceJid,
|
|
||||||
await aliceSession.getDeviceId(),
|
|
||||||
msg3.encryptedKeys,
|
|
||||||
getTimestamp(),
|
|
||||||
);
|
|
||||||
|
|
||||||
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,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
expect(messageText, aliceReceivedMessage);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:omemo_dart/omemo_dart.dart';
|
import 'package:omemo_dart/omemo_dart.dart';
|
||||||
import 'package:omemo_dart/src/protobuf/omemo_key_exchange.dart';
|
import 'package:omemo_dart/protobuf/schema.pb.dart';
|
||||||
import 'package:omemo_dart/src/trust/always.dart';
|
import 'package:omemo_dart/src/trust/always.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
@ -1304,10 +1304,10 @@ void main() {
|
|||||||
expect(aliceResult2.encryptedKeys.first.kex, true);
|
expect(aliceResult2.encryptedKeys.first.kex, true);
|
||||||
|
|
||||||
// The basic data should be the same
|
// The basic data should be the same
|
||||||
final parsedFirstKex = OmemoKeyExchange.fromBuffer(
|
final parsedFirstKex = OMEMOKeyExchange.fromBuffer(
|
||||||
base64.decode(aliceResult1.encryptedKeys.first.value),
|
base64.decode(aliceResult1.encryptedKeys.first.value),
|
||||||
);
|
);
|
||||||
final parsedSecondKex = OmemoKeyExchange.fromBuffer(
|
final parsedSecondKex = OMEMOKeyExchange.fromBuffer(
|
||||||
base64.decode(aliceResult2.encryptedKeys.first.value),
|
base64.decode(aliceResult2.encryptedKeys.first.value),
|
||||||
);
|
);
|
||||||
expect(parsedSecondKex.pkId, parsedFirstKex.pkId);
|
expect(parsedSecondKex.pkId, parsedFirstKex.pkId);
|
||||||
|
@ -1,186 +0,0 @@
|
|||||||
import 'package:omemo_dart/protobuf/schema.pb.dart';
|
|
||||||
import 'package:omemo_dart/src/protobuf/omemo_authenticated_message.dart';
|
|
||||||
import 'package:omemo_dart/src/protobuf/omemo_key_exchange.dart';
|
|
||||||
import 'package:omemo_dart/src/protobuf/omemo_message.dart';
|
|
||||||
import 'package:omemo_dart/src/protobuf/protobuf.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('Base 128 Varints', () {
|
|
||||||
test('Test simple parsing of Varints', () {
|
|
||||||
expect(
|
|
||||||
decodeVarint(<int>[1], 0).n,
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
decodeVarint(<int>[1], 0).length,
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
decodeVarint(<int>[0x96, 0x01, 0x00], 0).n,
|
|
||||||
150,
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
decodeVarint(<int>[0x96, 0x01, 0x00], 0).length,
|
|
||||||
2,
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
decodeVarint(<int>[172, 2, 0x8], 0).n,
|
|
||||||
300,
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
decodeVarint(<int>[172, 2, 0x8], 0).length,
|
|
||||||
2,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test encoding Varints', () {
|
|
||||||
expect(
|
|
||||||
encodeVarint(1),
|
|
||||||
<int>[1],
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
encodeVarint(150),
|
|
||||||
<int>[0x96, 0x01],
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
encodeVarint(300),
|
|
||||||
<int>[172, 2],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test some special cases', () {
|
|
||||||
expect(decodeVarint(encodeVarint(1042464893), 0).n, 1042464893);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group('OMEMOMessage', () {
|
|
||||||
test('Decode a OMEMOMessage', () {
|
|
||||||
final pbMessage = OMEMOMessage()
|
|
||||||
..n = 1
|
|
||||||
..pn = 5
|
|
||||||
..dhPub = <int>[1, 2, 3]
|
|
||||||
..ciphertext = <int>[4, 5, 6];
|
|
||||||
final serial = pbMessage.writeToBuffer();
|
|
||||||
final msg = OmemoMessage.fromBuffer(serial);
|
|
||||||
|
|
||||||
expect(msg.n, 1);
|
|
||||||
expect(msg.pn, 5);
|
|
||||||
expect(msg.dhPub, <int>[1, 2, 3]);
|
|
||||||
expect(msg.ciphertext, <int>[4, 5, 6]);
|
|
||||||
});
|
|
||||||
test('Decode a OMEMOMessage without ciphertext', () {
|
|
||||||
final pbMessage = OMEMOMessage()
|
|
||||||
..n = 1
|
|
||||||
..pn = 5
|
|
||||||
..dhPub = <int>[1, 2, 3];
|
|
||||||
final serial = pbMessage.writeToBuffer();
|
|
||||||
final msg = OmemoMessage.fromBuffer(serial);
|
|
||||||
|
|
||||||
expect(msg.n, 1);
|
|
||||||
expect(msg.pn, 5);
|
|
||||||
expect(msg.dhPub, <int>[1, 2, 3]);
|
|
||||||
expect(msg.ciphertext, null);
|
|
||||||
});
|
|
||||||
test('Encode a OMEMOMessage', () {
|
|
||||||
final m = OmemoMessage()
|
|
||||||
..n = 1
|
|
||||||
..pn = 5
|
|
||||||
..dhPub = <int>[1, 2, 3]
|
|
||||||
..ciphertext = <int>[4, 5, 6];
|
|
||||||
final serial = m.writeToBuffer();
|
|
||||||
final msg = OMEMOMessage.fromBuffer(serial);
|
|
||||||
|
|
||||||
expect(msg.n, 1);
|
|
||||||
expect(msg.pn, 5);
|
|
||||||
expect(msg.dhPub, <int>[1, 2, 3]);
|
|
||||||
expect(msg.ciphertext, <int>[4, 5, 6]);
|
|
||||||
});
|
|
||||||
test('Encode a OMEMOMessage without ciphertext', () {
|
|
||||||
final m = OmemoMessage()
|
|
||||||
..n = 1
|
|
||||||
..pn = 5
|
|
||||||
..dhPub = <int>[1, 2, 3];
|
|
||||||
final serial = m.writeToBuffer();
|
|
||||||
final msg = OMEMOMessage.fromBuffer(serial);
|
|
||||||
|
|
||||||
expect(msg.n, 1);
|
|
||||||
expect(msg.pn, 5);
|
|
||||||
expect(msg.dhPub, <int>[1, 2, 3]);
|
|
||||||
expect(msg.ciphertext, <int>[]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group('OMEMOAuthenticatedMessage', () {
|
|
||||||
test('Test encoding a message', () {
|
|
||||||
final msg = OmemoAuthenticatedMessage()
|
|
||||||
..mac = <int>[1, 2, 3]
|
|
||||||
..message = <int>[4, 5, 6];
|
|
||||||
final decoded = OMEMOAuthenticatedMessage.fromBuffer(msg.writeToBuffer());
|
|
||||||
|
|
||||||
expect(decoded.mac, <int>[1, 2, 3]);
|
|
||||||
expect(decoded.message, <int>[4, 5, 6]);
|
|
||||||
});
|
|
||||||
test('Test decoding a message', () {
|
|
||||||
final msg = OMEMOAuthenticatedMessage()
|
|
||||||
..mac = <int>[1, 2, 3]
|
|
||||||
..message = <int>[4, 5, 6];
|
|
||||||
final bytes = msg.writeToBuffer();
|
|
||||||
final decoded = OmemoAuthenticatedMessage.fromBuffer(bytes);
|
|
||||||
|
|
||||||
expect(decoded.mac, <int>[1, 2, 3]);
|
|
||||||
expect(decoded.message, <int>[4, 5, 6]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group('OMEMOKeyExchange', () {
|
|
||||||
test('Test encoding a message', () {
|
|
||||||
final authMessage = OmemoAuthenticatedMessage()
|
|
||||||
..mac = <int>[5, 6, 8, 0]
|
|
||||||
..message = <int>[4, 5, 7, 3, 2];
|
|
||||||
final message = OmemoKeyExchange()
|
|
||||||
..pkId = 698
|
|
||||||
..spkId = 245
|
|
||||||
..ik = <int>[1, 4, 6]
|
|
||||||
..ek = <int>[4, 6, 7, 80]
|
|
||||||
..message = authMessage;
|
|
||||||
final kex = OMEMOKeyExchange.fromBuffer(message.writeToBuffer());
|
|
||||||
|
|
||||||
expect(kex.pkId, 698);
|
|
||||||
expect(kex.spkId, 245);
|
|
||||||
expect(kex.ik, <int>[1, 4, 6]);
|
|
||||||
expect(kex.ek, <int>[4, 6, 7, 80]);
|
|
||||||
|
|
||||||
expect(kex.message.mac, <int>[5, 6, 8, 0]);
|
|
||||||
expect(kex.message.message, <int>[4, 5, 7, 3, 2]);
|
|
||||||
});
|
|
||||||
test('Test decoding a message', () {
|
|
||||||
final message = OMEMOAuthenticatedMessage()
|
|
||||||
..mac = <int>[5, 6, 8, 0]
|
|
||||||
..message = <int>[4, 5, 7, 3, 2];
|
|
||||||
final kex = OMEMOKeyExchange()
|
|
||||||
..pkId = 698
|
|
||||||
..spkId = 245
|
|
||||||
..ik = <int>[1, 4, 6]
|
|
||||||
..ek = <int>[4, 6, 7, 80]
|
|
||||||
..message = message;
|
|
||||||
final decoded = OmemoKeyExchange.fromBuffer(kex.writeToBuffer());
|
|
||||||
|
|
||||||
expect(decoded.pkId, 698);
|
|
||||||
expect(decoded.spkId, 245);
|
|
||||||
expect(decoded.ik, <int>[1, 4, 6]);
|
|
||||||
expect(decoded.ek, <int>[4, 6, 7, 80]);
|
|
||||||
|
|
||||||
expect(decoded.message!.mac, <int>[5, 6, 8, 0]);
|
|
||||||
expect(decoded.message!.message, <int>[4, 5, 7, 3, 2]);
|
|
||||||
});
|
|
||||||
test('Test decoding an issue', () {
|
|
||||||
/*
|
|
||||||
final data = 'CAAQfRogc2GwslU219dUkrMHNM4KdZRmuFnBTae+bQaJ+55IsAMiII7aZKj2sUpb6xR/3Ari7WZUmKFV0G6czUc4NMvjKDBaKnwKEM2ZpI8X3TgcxhxwENANnlsSaAgAEAAaICy8T9WPgLb7RdYd8/4JkrLF0RahEkC3ZaEfk5jw3dsLIkBMILzLyByweLgF4lCn0oNea+kbdrFr6rY7r/7WyI8hXEQz38QpnN+jyGGwC7Ga0dq70WuyqE7VpiFArQwqZh2G';
|
|
||||||
final kex = OmemoKeyExchange.fromBuffer(base64Decode(data));
|
|
||||||
|
|
||||||
expect(kex.spkId!, 1042464893);
|
|
||||||
*/
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
@ -10,12 +10,8 @@ Map<String, dynamic> jsonify(Map<String, dynamic> map) {
|
|||||||
void main() {
|
void main() {
|
||||||
test('Test serialising and deserialising the Device', () async {
|
test('Test serialising and deserialising the Device', () async {
|
||||||
// Generate a random session
|
// Generate a random session
|
||||||
final oldSession = await OmemoSessionManager.generateNewIdentity(
|
final oldDevice =
|
||||||
'user@test.server',
|
await OmemoDevice.generateNewDevice('user@test.server', opkAmount: 5);
|
||||||
AlwaysTrustingTrustManager(),
|
|
||||||
opkAmount: 1,
|
|
||||||
);
|
|
||||||
final oldDevice = await oldSession.getDevice();
|
|
||||||
final serialised = jsonify(await oldDevice.toJson());
|
final serialised = jsonify(await oldDevice.toJson());
|
||||||
|
|
||||||
final newDevice = OmemoDevice.fromJson(serialised);
|
final newDevice = OmemoDevice.fromJson(serialised);
|
||||||
@ -25,24 +21,20 @@ void main() {
|
|||||||
test('Test serialising and deserialising the Device after rotating the SPK',
|
test('Test serialising and deserialising the Device after rotating the SPK',
|
||||||
() async {
|
() async {
|
||||||
// Generate a random session
|
// Generate a random session
|
||||||
final oldSession = await OmemoSessionManager.generateNewIdentity(
|
final device =
|
||||||
'user@test.server',
|
await OmemoDevice.generateNewDevice('user@test.server', opkAmount: 1);
|
||||||
AlwaysTrustingTrustManager(),
|
final oldDevice = await device.replaceSignedPrekey();
|
||||||
opkAmount: 1,
|
|
||||||
);
|
|
||||||
final oldDevice =
|
|
||||||
await (await oldSession.getDevice()).replaceSignedPrekey();
|
|
||||||
final serialised = jsonify(await oldDevice.toJson());
|
final serialised = jsonify(await oldDevice.toJson());
|
||||||
|
|
||||||
final newDevice = OmemoDevice.fromJson(serialised);
|
final newDevice = OmemoDevice.fromJson(serialised);
|
||||||
expect(await oldDevice.equals(newDevice), true);
|
expect(await oldDevice.equals(newDevice), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Test serialising and deserialising the OmemoDoubleRatchet', () async {
|
/*test('Test serialising and deserialising the OmemoDoubleRatchet', () async {
|
||||||
// Generate a random ratchet
|
// Generate a random ratchet
|
||||||
const aliceJid = 'alice@server.example';
|
const aliceJid = 'alice@server.example';
|
||||||
const bobJid = 'bob@other.server.example';
|
const bobJid = 'bob@other.server.example';
|
||||||
final aliceSession = await OmemoSessionManager.generateNewIdentity(
|
final aliceManager = OmemoSessionManager.generateNewIdentity(
|
||||||
aliceJid,
|
aliceJid,
|
||||||
AlwaysTrustingTrustManager(),
|
AlwaysTrustingTrustManager(),
|
||||||
opkAmount: 1,
|
opkAmount: 1,
|
||||||
@ -151,5 +143,5 @@ void main() {
|
|||||||
serialized,
|
serialized,
|
||||||
);
|
);
|
||||||
expect(btbv.enablementCache, enableCache);
|
expect(btbv.enablementCache, enableCache);
|
||||||
});
|
});*/
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user