feat: Track the old Signed Prekey after rotation

This commit is contained in:
PapaTutuWawa 2022-08-08 15:32:08 +02:00
parent 9c23721904
commit b8b6bbf800
6 changed files with 95 additions and 21 deletions

View File

@ -3,7 +3,6 @@ import 'package:cryptography/cryptography.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.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/helpers.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';

View File

@ -1,18 +0,0 @@
import 'dart:convert';
import 'package:cryptography/cryptography.dart';
import 'package:omemo_dart/src/keys.dart';
OmemoPublicKey? decodeKeyIfNotNull(Map<String, dynamic> map, String key, KeyPairType type) {
if (map[key] == null) return null;
return OmemoPublicKey.fromBytes(
base64.decode(map[key]! as String),
type,
);
}
List<int>? base64DecodeIfNotNull(Map<String, dynamic> map, String key) {
if (map[key] == null) return null;
return base64.decode(map[key]! as String);
}

View File

@ -1,4 +1,7 @@
import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:cryptography/cryptography.dart';
import 'package:omemo_dart/src/keys.dart';
/// Flattens [inputs] and concatenates the elements. /// Flattens [inputs] and concatenates the elements.
List<int> concat(List<List<int>> inputs) { List<int> concat(List<List<int>> inputs) {
@ -39,3 +42,34 @@ List<int> generateRandomBytes(int length) {
int generateRandom32BitNumber() { int generateRandom32BitNumber() {
return Random.secure().nextInt(4294967295 /*pow(2, 32) - 1*/); return Random.secure().nextInt(4294967295 /*pow(2, 32) - 1*/);
} }
OmemoPublicKey? decodeKeyIfNotNull(Map<String, dynamic> map, String key, KeyPairType type) {
if (map[key] == null) return null;
return OmemoPublicKey.fromBytes(
base64.decode(map[key]! as String),
type,
);
}
List<int>? base64DecodeIfNotNull(Map<String, dynamic> map, String key) {
if (map[key] == null) return null;
return base64.decode(map[key]! as String);
}
String? base64EncodeIfNotNull(List<int>? bytes) {
if (bytes == null) return null;
return base64.encode(bytes);
}
OmemoKeyPair? decodeKeyPairIfNotNull(String? pk, String? sk, KeyPairType type) {
if (pk == null || sk == null) return null;
return OmemoKeyPair.fromBytes(
base64.decode(pk),
base64.decode(sk),
type,
);
}

View File

@ -10,7 +10,18 @@ import 'package:omemo_dart/src/x3dh/x3dh.dart';
@immutable @immutable
class Device { class Device {
const Device(this.jid, this.id, this.ik, this.spk, this.spkId, this.spkSignature, this.opks); const Device(
this.jid,
this.id,
this.ik,
this.spk,
this.spkId,
this.spkSignature,
this.oldSpk,
this.oldSpkId,
this.oldSpkSignature,
this.opks,
);
/// Deserialize the Device /// Deserialize the Device
factory Device.fromJson(Map<String, dynamic> data) { factory Device.fromJson(Map<String, dynamic> data) {
@ -27,6 +38,10 @@ class Device {
'spk_pub': 'base/64/encoded', 'spk_pub': 'base/64/encoded',
'spk_id': 123, 'spk_id': 123,
'spk_sig': 'base/64/encoded', 'spk_sig': 'base/64/encoded',
'old_spk': 'base/64/encoded',
'old_spk_pub': 'base/64/encoded',
'old_spk_id': 122,
'old_ spk_sig': 'base/64/encoded',
'opks': [ 'opks': [
{ {
'id': 0, 'id': 0,
@ -60,6 +75,13 @@ class Device {
), ),
data['spk_id']! as int, data['spk_id']! as int,
base64.decode(data['spk_sig']! as String), base64.decode(data['spk_sig']! as String),
decodeKeyPairIfNotNull(
data['old_spk_pub'] as String?,
data['old_spk'] as String?,
KeyPairType.x25519,
),
data['old_spk_id'] as int?,
base64DecodeIfNotNull(data, 'old_spk_sig'),
opks, opks,
); );
} }
@ -77,7 +99,7 @@ class Device {
opks[i] = await OmemoKeyPair.generateNewPair(KeyPairType.x25519); opks[i] = await OmemoKeyPair.generateNewPair(KeyPairType.x25519);
} }
return Device(jid, id, ik, spk, spkId, signature, opks); return Device(jid, id, ik, spk, spkId, signature, null, null, null, opks);
} }
/// Our bare Jid /// Our bare Jid
@ -96,6 +118,13 @@ class Device {
/// ...and its signature /// ...and its signature
final List<int> spkSignature; final List<int> spkSignature;
/// The old Signed Prekey...
final OmemoKeyPair? oldSpk;
/// its Id, ...
final int? oldSpkId;
/// ...and its signature
final List<int>? oldSpkSignature;
/// Map of an id to the associated Onetime-Prekey /// Map of an id to the associated Onetime-Prekey
final Map<int, OmemoKeyPair> opks; final Map<int, OmemoKeyPair> opks;
@ -112,6 +141,9 @@ class Device {
spk, spk,
spkId, spkId,
spkSignature, spkSignature,
oldSpk,
oldSpkId,
oldSpkSignature,
opks, opks,
); );
} }
@ -131,6 +163,9 @@ class Device {
newSpk, newSpk,
newSpkId, newSpkId,
newSignature, newSignature,
spk,
spkId,
spkSignature,
opks, opks,
); );
} }
@ -175,6 +210,10 @@ class Device {
'spk_pub': base64.encode(await spk.pk.getBytes()), 'spk_pub': base64.encode(await spk.pk.getBytes()),
'spk_id': spkId, 'spk_id': spkId,
'spk_sig': base64.encode(spkSignature), 'spk_sig': base64.encode(spkSignature),
'old_spk': base64EncodeIfNotNull(await oldSpk?.sk.getBytes()),
'old_spk_pub': base64EncodeIfNotNull(await oldSpk?.pk.getBytes()),
'old_spk_id': oldSpkId,
'old_spk_sig': base64EncodeIfNotNull(oldSpkSignature),
'opks': serialisedOpks, 'opks': serialisedOpks,
}; };
} }
@ -198,12 +237,18 @@ class Device {
final ikMatch = await ik.equals(other.ik); final ikMatch = await ik.equals(other.ik);
// ignore: invalid_use_of_visible_for_testing_member // ignore: invalid_use_of_visible_for_testing_member
final spkMatch = await spk.equals(other.spk); final spkMatch = await spk.equals(other.spk);
// ignore: invalid_use_of_visible_for_testing_member
final oldSpkMatch = oldSpk != null ? await oldSpk!.equals(other.oldSpk!) : other.oldSpk == null;
final oldSpkSigsMatch = oldSpkSignature != null ? listsEqual(oldSpkSignature!, other.oldSpkSignature!) : other.oldSpkSignature == null;
return id == other.id && return id == other.id &&
ikMatch && ikMatch &&
spkMatch && spkMatch &&
oldSpkMatch &&
jid == other.jid && jid == other.jid &&
listsEqual(spkSignature, other.spkSignature) && listsEqual(spkSignature, other.spkSignature) &&
spkId == other.spkId && spkId == other.spkId &&
oldSpkId == other.oldSpkId &&
oldSpkSigsMatch &&
opksMatch; opksMatch;
} }
} }

View File

@ -244,5 +244,9 @@ void main() {
expect(await oldDevice.equals(newDevice!), false); expect(await oldDevice.equals(newDevice!), false);
expect(await newDevice!.equals(await aliceSession.getDevice()), true); expect(await newDevice!.equals(await aliceSession.getDevice()), true);
expect(await newDevice!.oldSpk!.equals(oldDevice.spk), true);
expect(newDevice!.oldSpkId, oldDevice.spkId);
expect(listsEqual(newDevice!.oldSpkSignature!, oldDevice.spkSignature), true);
}); });
} }

View File

@ -12,6 +12,16 @@ void main() {
expect(await oldDevice.equals(newDevice), true); expect(await oldDevice.equals(newDevice), true);
}); });
test('Test serialising and deserialising the Device after rotating the SPK', () async {
// Generate a random session
final oldSession = await OmemoSessionManager.generateNewIdentity('user@test.server', opkAmount: 1);
final oldDevice = await (await oldSession.getDevice()).replaceSignedPrekey();
final serialised = await oldDevice.toJson();
final newDevice = Device.fromJson(serialised);
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';