feat: _decryptMessage explicitly tells us that a ratchet was created

This commit is contained in:
PapaTutuWawa 2022-12-27 00:49:16 +01:00
parent 54eeb816eb
commit 6c4dd62c5a
2 changed files with 49 additions and 33 deletions

View File

@ -28,6 +28,12 @@ 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';
class _InternalDecryptionResult {
const _InternalDecryptionResult(this.ratchetCreated, this.payload);
final bool ratchetCreated;
final String? payload;
}
class OmemoManager { class OmemoManager {
OmemoManager( OmemoManager(
this._device, this._device,
@ -246,7 +252,7 @@ class OmemoManager {
/// element, then [ciphertext] must be set to null. In this case, this function /// element, then [ciphertext] must be set to null. In this case, this function
/// will return null as there is no message to be decrypted. This, however, is used /// will return null as there is no message to be decrypted. This, however, is used
/// to set up sessions or advance the ratchets. /// to set up sessions or advance the ratchets.
Future<String?> _decryptMessage(List<int>? ciphertext, String senderJid, int senderDeviceId, List<EncryptedKey> keys, int timestamp) async { Future<_InternalDecryptionResult> _decryptMessage(List<int>? ciphertext, String senderJid, int senderDeviceId, List<EncryptedKey> keys, int timestamp) async {
// Try to find a session we can decrypt with. // Try to find a session we can decrypt with.
var device = await getDevice(); var device = await getDevice();
final rawKey = keys.firstWhereOrNull((key) => key.rid == device.id); final rawKey = keys.firstWhereOrNull((key) => key.rid == device.id);
@ -260,6 +266,7 @@ class OmemoManager {
OmemoAuthenticatedMessage authMessage; OmemoAuthenticatedMessage authMessage;
OmemoDoubleRatchet? oldRatchet; OmemoDoubleRatchet? oldRatchet;
OmemoMessage? message; OmemoMessage? message;
var ratchetCreated = false;
if (rawKey.kex) { if (rawKey.kex) {
// 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.
@ -294,7 +301,10 @@ class OmemoManager {
decrypted, decrypted,
); );
_addSession(senderJid, senderDeviceId, oldRatchet); _addSession(senderJid, senderDeviceId, oldRatchet);
return plaintext; return _InternalDecryptionResult(
true,
plaintext,
);
} catch (_) { } catch (_) {
_log.finest('Failed to use old ratchet with KEX for existing ratchet'); _log.finest('Failed to use old ratchet with KEX for existing ratchet');
} }
@ -303,6 +313,7 @@ class OmemoManager {
final r = await _addSessionFromKeyExchange(senderJid, senderDeviceId, kex); final r = await _addSessionFromKeyExchange(senderJid, senderDeviceId, kex);
await _trustManager.onNewSession(senderJid, senderDeviceId); await _trustManager.onNewSession(senderJid, senderDeviceId);
_addSession(senderJid, senderDeviceId, r); _addSession(senderJid, senderDeviceId, r);
ratchetCreated = true;
// Replace the OPK // Replace the OPK
// TODO(PapaTutuWawa): Replace the OPK when we know that the KEX worked // TODO(PapaTutuWawa): Replace the OPK when we know that the KEX worked
@ -348,7 +359,10 @@ class OmemoManager {
); );
try { try {
return _decryptAndVerifyHmac(ciphertext, keyAndHmac); return _InternalDecryptionResult(
ratchetCreated,
await _decryptAndVerifyHmac(ciphertext, keyAndHmac),
);
} catch (_) { } catch (_) {
_restoreRatchet(ratchetKey, oldRatchet); _restoreRatchet(ratchetKey, oldRatchet);
rethrow; rethrow;
@ -519,10 +533,9 @@ class OmemoManager {
await _enterRatchetCriticalSection(stanza.bareSenderJid); await _enterRatchetCriticalSection(stanza.bareSenderJid);
final ratchetKey = RatchetMapKey(stanza.bareSenderJid, stanza.senderDeviceId); final ratchetKey = RatchetMapKey(stanza.bareSenderJid, stanza.senderDeviceId);
final ratchetCreated = !_ratchetMap.containsKey(ratchetKey); final _InternalDecryptionResult result;
String? payload;
try { try {
payload = await _decryptMessage( result = await _decryptMessage(
base64.decode(stanza.payload), base64.decode(stanza.payload),
stanza.bareSenderJid, stanza.bareSenderJid,
stanza.senderDeviceId, stanza.senderDeviceId,
@ -541,7 +554,9 @@ class OmemoManager {
final ratchet = _getRatchet(ratchetKey); final ratchet = _getRatchet(ratchetKey);
assert(ratchet != null, 'We decrypted the message, so the ratchet must exist'); assert(ratchet != null, 'We decrypted the message, so the ratchet must exist');
if (ratchet!.nr > 53) { if (ratchet!.acknowledged) {
// Ratchet is acknowledged
if (ratchet.nr > 53 || result.ratchetCreated) {
await sendEmptyOmemoMessage( await sendEmptyOmemoMessage(
await _encryptToJids( await _encryptToJids(
[stanza.bareSenderJid], [stanza.bareSenderJid],
@ -552,15 +567,14 @@ class OmemoManager {
} }
// Ratchet is acked // Ratchet is acked
if (!ratchetCreated && ratchet.acknowledged) {
await _leaveRatchetCriticalSection(stanza.bareSenderJid); await _leaveRatchetCriticalSection(stanza.bareSenderJid);
return DecryptionResult( return DecryptionResult(
payload, result.payload,
null, null,
); );
} } else {
// Ratchet is not acked.
// Ratchet is not acked. Mark as acked and send an empty OMEMO message. // Mark as acked and send an empty OMEMO message.
await ratchetAcknowledged( await ratchetAcknowledged(
stanza.bareSenderJid, stanza.bareSenderJid,
stanza.senderDeviceId, stanza.senderDeviceId,
@ -576,10 +590,11 @@ class OmemoManager {
await _leaveRatchetCriticalSection(stanza.bareSenderJid); await _leaveRatchetCriticalSection(stanza.bareSenderJid);
return DecryptionResult( return DecryptionResult(
payload, result.payload,
null, null,
); );
} }
}
/// Call when sending out an encrypted stanza. Will handle everything and /// Call when sending out an encrypted stanza. Will handle everything and
/// encrypt it. /// encrypt it.

View File

@ -71,9 +71,10 @@ void main() {
), ),
); );
expect(bobResult.payload, 'Hello world');
expect(bobResult.error, null);
expect(aliceEmptyMessageSent, 0); expect(aliceEmptyMessageSent, 0);
expect(bobEmptyMessageSent, 1); expect(bobEmptyMessageSent, 1);
expect(bobResult.payload, 'Hello world');
// Alice receives the ack message // Alice receives the ack message
await aliceManager.ratchetAcknowledged( await aliceManager.ratchetAcknowledged(