feat: Each OPK now gets it's own unique id

This commit is contained in:
PapaTutuWawa 2023-06-17 20:50:01 +02:00
parent 234fee167f
commit 65c0975a77
3 changed files with 68 additions and 6 deletions

View File

@ -3,7 +3,15 @@ import 'package:omemo_dart/src/errors.dart';
@immutable @immutable
class DecryptionResult { class DecryptionResult {
const DecryptionResult(this.payload, this.error); const DecryptionResult(this.payload, this.usedOpkId, this.error);
/// The decrypted payload or null, if it was an empty OMEMO message.
final String? payload; final String? payload;
/// In case a key exchange has been performed: The id of the used OPK. Useful for
/// replacing the OPK after a message catch-up.
final int? usedOpkId;
/// The error that occurred during decryption or null, if no error occurred.
final OmemoError? error; final OmemoError? error;
} }

View File

@ -35,7 +35,16 @@ class OmemoDevice {
final opks = <int, OmemoKeyPair>{}; final opks = <int, OmemoKeyPair>{};
for (var i = 0; i < opkAmount; i++) { for (var i = 0; i < opkAmount; i++) {
opks[i] = await OmemoKeyPair.generateNewPair(KeyPairType.x25519); // Generate unique ids for each key
while (true) {
final opkId = generateRandom32BitNumber();
if (opks.containsKey(opkId)) {
continue;
}
opks[opkId] = await OmemoKeyPair.generateNewPair(KeyPairType.x25519);
break;
}
} }
return OmemoDevice(jid, id, ik, spk, spkId, signature, null, null, opks); return OmemoDevice(jid, id, ik, spk, spkId, signature, null, null, opks);
@ -72,7 +81,18 @@ class OmemoDevice {
/// a new Device object that copies over everything but replaces said key. /// a new Device object that copies over everything but replaces said key.
@internal @internal
Future<OmemoDevice> replaceOnetimePrekey(int id) async { Future<OmemoDevice> replaceOnetimePrekey(int id) async {
opks[id] = await OmemoKeyPair.generateNewPair(KeyPairType.x25519); opks.remove(id);
// Generate a new unique id for the OPK.
while (true) {
final newId = generateRandom32BitNumber();
if (opks.containsKey(newId)) {
continue;
}
opks[newId] = await OmemoKeyPair.generateNewPair(KeyPairType.x25519);
break;
}
return OmemoDevice( return OmemoDevice(
jid, jid,

View File

@ -351,6 +351,7 @@ class OmemoManager {
final key = stanza.keys.firstWhereOrNull((key) => key.rid == deviceId); final key = stanza.keys.firstWhereOrNull((key) => key.rid == deviceId);
if (key == null) { if (key == null) {
return DecryptionResult( return DecryptionResult(
null,
null, null,
NotEncryptedForDeviceError(), NotEncryptedForDeviceError(),
); );
@ -359,6 +360,7 @@ class OmemoManager {
// Protobuf will happily parse this and return bogus data. // Protobuf will happily parse this and return bogus data.
if (key.value.isEmpty) { if (key.value.isEmpty) {
return DecryptionResult( return DecryptionResult(
null,
null, null,
MalformedEncryptedKeyError(), MalformedEncryptedKeyError(),
); );
@ -396,6 +398,7 @@ class OmemoManager {
spk = device.oldSpk!; spk = device.oldSpk!;
} else { } else {
return DecryptionResult( return DecryptionResult(
null,
null, null,
UnknownSignedPrekeyError(), UnknownSignedPrekeyError(),
); );
@ -438,7 +441,7 @@ class OmemoManager {
final error = keyAndHmac.get<OmemoError>(); final error = keyAndHmac.get<OmemoError>();
_log.warning('Failed to decrypt symmetric key: $error'); _log.warning('Failed to decrypt symmetric key: $error');
return DecryptionResult(null, error); return DecryptionResult(null, null, error);
} }
Result<OmemoError, String?> result; Result<OmemoError, String?> result;
@ -452,6 +455,7 @@ class OmemoManager {
_log.warning('Decrypting payload failed: $error'); _log.warning('Decrypting payload failed: $error');
return DecryptionResult( return DecryptionResult(
null,
null, null,
error, error,
); );
@ -503,6 +507,7 @@ class OmemoManager {
return DecryptionResult( return DecryptionResult(
result.get<String?>(), result.get<String?>(),
kexMessage.pkId,
null, null,
); );
} else { } else {
@ -517,6 +522,7 @@ class OmemoManager {
await _sendOmemoHeartbeat(stanza.bareSenderJid); await _sendOmemoHeartbeat(stanza.bareSenderJid);
return DecryptionResult( return DecryptionResult(
null,
null, null,
NoSessionWithDeviceError(), NoSessionWithDeviceError(),
); );
@ -540,7 +546,7 @@ class OmemoManager {
if (keyAndHmac.isType<OmemoError>()) { if (keyAndHmac.isType<OmemoError>()) {
final error = keyAndHmac.get<OmemoError>(); final error = keyAndHmac.get<OmemoError>();
_log.warning('Failed to decrypt symmetric key: $error'); _log.warning('Failed to decrypt symmetric key: $error');
return DecryptionResult(null, error); return DecryptionResult(null, null, error);
} }
Result<OmemoError, String?> result; Result<OmemoError, String?> result;
@ -553,6 +559,7 @@ class OmemoManager {
final error = result.get<OmemoError>(); final error = result.get<OmemoError>();
_log.warning('Failed to decrypt message: $error'); _log.warning('Failed to decrypt message: $error');
return DecryptionResult( return DecryptionResult(
null,
null, null,
error, error,
); );
@ -586,6 +593,7 @@ class OmemoManager {
return DecryptionResult( return DecryptionResult(
result.get<String?>(), result.get<String?>(),
null, null,
null,
); );
} }
} }
@ -958,7 +966,33 @@ class OmemoManager {
@visibleForTesting @visibleForTesting
OmemoDoubleRatchet? getRatchet(RatchetMapKey key) => _ratchetMap[key]; OmemoDoubleRatchet? getRatchet(RatchetMapKey key) => _ratchetMap[key];
/// Trust management functions /// Replaces the OPK with id [opkId] and commits the new device to storage. This
/// function should not be called. It's only useful for rotating OPKs after message
/// catch-up, because in that case the OPKs are not rotated automatically.
Future<void> replaceOnetimePrekey(int opkId) async {
await _deviceLock.synchronized(() async {
// Replace OPK
await _device.replaceOnetimePrekey(opkId);
// Commit the device
await commitDevice(_device);
});
}
/// Replaces the SPK of our device and commits it to storage.
Future<void> replaceSignedPrekey() async {
await _deviceLock.synchronized(() async {
// Replace SPK
await _device.replaceSignedPrekey();
// Commit the device
await commitDevice(_device);
});
}
/// Acquire a lock for interacting with the trust manager for modifying the trust
/// state of [jid]. [callback] is called from within the critical section with the
/// trust manager as its parameter.
Future<void> withTrustManager( Future<void> withTrustManager(
String jid, String jid,
Future<void> Function(TrustManager) callback, Future<void> Function(TrustManager) callback,