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
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;
/// 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;
}

View File

@ -35,7 +35,16 @@ class OmemoDevice {
final opks = <int, OmemoKeyPair>{};
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);
@ -72,7 +81,18 @@ class OmemoDevice {
/// a new Device object that copies over everything but replaces said key.
@internal
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(
jid,

View File

@ -351,6 +351,7 @@ class OmemoManager {
final key = stanza.keys.firstWhereOrNull((key) => key.rid == deviceId);
if (key == null) {
return DecryptionResult(
null,
null,
NotEncryptedForDeviceError(),
);
@ -359,6 +360,7 @@ class OmemoManager {
// Protobuf will happily parse this and return bogus data.
if (key.value.isEmpty) {
return DecryptionResult(
null,
null,
MalformedEncryptedKeyError(),
);
@ -396,6 +398,7 @@ class OmemoManager {
spk = device.oldSpk!;
} else {
return DecryptionResult(
null,
null,
UnknownSignedPrekeyError(),
);
@ -438,7 +441,7 @@ class OmemoManager {
final error = keyAndHmac.get<OmemoError>();
_log.warning('Failed to decrypt symmetric key: $error');
return DecryptionResult(null, error);
return DecryptionResult(null, null, error);
}
Result<OmemoError, String?> result;
@ -452,6 +455,7 @@ class OmemoManager {
_log.warning('Decrypting payload failed: $error');
return DecryptionResult(
null,
null,
error,
);
@ -503,6 +507,7 @@ class OmemoManager {
return DecryptionResult(
result.get<String?>(),
kexMessage.pkId,
null,
);
} else {
@ -517,6 +522,7 @@ class OmemoManager {
await _sendOmemoHeartbeat(stanza.bareSenderJid);
return DecryptionResult(
null,
null,
NoSessionWithDeviceError(),
);
@ -540,7 +546,7 @@ class OmemoManager {
if (keyAndHmac.isType<OmemoError>()) {
final error = keyAndHmac.get<OmemoError>();
_log.warning('Failed to decrypt symmetric key: $error');
return DecryptionResult(null, error);
return DecryptionResult(null, null, error);
}
Result<OmemoError, String?> result;
@ -553,6 +559,7 @@ class OmemoManager {
final error = result.get<OmemoError>();
_log.warning('Failed to decrypt message: $error');
return DecryptionResult(
null,
null,
error,
);
@ -586,6 +593,7 @@ class OmemoManager {
return DecryptionResult(
result.get<String?>(),
null,
null,
);
}
}
@ -958,7 +966,33 @@ class OmemoManager {
@visibleForTesting
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(
String jid,
Future<void> Function(TrustManager) callback,