feat: Allow calculating fingerprints
This commit is contained in:
parent
70ab74b8dc
commit
dd96e840d4
@ -7,5 +7,6 @@ export 'src/helpers.dart';
|
||||
export 'src/keys.dart';
|
||||
export 'src/omemo/bundle.dart';
|
||||
export 'src/omemo/device.dart';
|
||||
export 'src/omemo/fingerprint.dart';
|
||||
export 'src/omemo/sessionmanager.dart';
|
||||
export 'src/x3dh/x3dh.dart';
|
||||
|
@ -65,8 +65,9 @@ class OmemoDoubleRatchet {
|
||||
this.ns, // Ns
|
||||
this.nr, // Nr
|
||||
this.pn, // Pn
|
||||
this.ik,
|
||||
this.sessionAd,
|
||||
this.mkSkipped,
|
||||
this.mkSkipped, // MKSKIPPED
|
||||
);
|
||||
|
||||
factory OmemoDoubleRatchet.fromJson(Map<String, dynamic> data) {
|
||||
@ -81,6 +82,7 @@ class OmemoDoubleRatchet {
|
||||
'ns': 0,
|
||||
'nr': 0,
|
||||
'pn': 0,
|
||||
'ik_pub': 'base/64/encoded',
|
||||
'session_ad': 'base/64/encoded',
|
||||
'mkskipped': [
|
||||
{
|
||||
@ -110,6 +112,10 @@ class OmemoDoubleRatchet {
|
||||
data['ns']! as int,
|
||||
data['nr']! as int,
|
||||
data['pn']! as int,
|
||||
OmemoPublicKey.fromBytes(
|
||||
base64.decode(data['ik_pub']! as String),
|
||||
KeyPairType.ed25519,
|
||||
),
|
||||
base64.decode(data['session_ad']! as String),
|
||||
mkSkipped,
|
||||
);
|
||||
@ -135,14 +141,18 @@ class OmemoDoubleRatchet {
|
||||
/// Previous sending chain number
|
||||
int pn;
|
||||
|
||||
/// The IK public key from the chat partner. Not used for the actual encryption but
|
||||
/// for verification purposes
|
||||
final OmemoPublicKey ik;
|
||||
|
||||
final List<int> sessionAd;
|
||||
|
||||
final Map<SkippedKey, List<int>> mkSkipped;
|
||||
|
||||
/// Create an OMEMO session using the Signed Pre Key [spk], the shared secret [sk] that
|
||||
/// was obtained using a X3DH and the associated data [ad] that was also obtained through
|
||||
/// a X3DH.
|
||||
static Future<OmemoDoubleRatchet> initiateNewSession(OmemoPublicKey spk, List<int> sk, List<int> ad) async {
|
||||
/// a X3DH. [ik] refers to Bob's (the receiver's) IK public key.
|
||||
static Future<OmemoDoubleRatchet> initiateNewSession(OmemoPublicKey spk, OmemoPublicKey ik, List<int> sk, List<int> ad) async {
|
||||
final dhs = await OmemoKeyPair.generateNewPair(KeyPairType.x25519);
|
||||
final dhr = spk;
|
||||
final rk = await kdfRk(sk, await omemoDH(dhs, dhr, 0));
|
||||
@ -157,6 +167,7 @@ class OmemoDoubleRatchet {
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
ik,
|
||||
ad,
|
||||
{},
|
||||
);
|
||||
@ -164,8 +175,9 @@ class OmemoDoubleRatchet {
|
||||
|
||||
/// Create an OMEMO session that was not initiated by the caller using the used Signed
|
||||
/// Pre Key keypair [spk], the shared secret [sk] that was obtained through a X3DH and
|
||||
/// the associated data [ad] that was also obtained through a X3DH.
|
||||
static Future<OmemoDoubleRatchet> acceptNewSession(OmemoKeyPair spk, List<int> sk, List<int> ad) async {
|
||||
/// the associated data [ad] that was also obtained through a X3DH. [ik] refers to
|
||||
/// Alice's (the initiator's) IK public key.
|
||||
static Future<OmemoDoubleRatchet> acceptNewSession(OmemoKeyPair spk, OmemoPublicKey ik, List<int> sk, List<int> ad) async {
|
||||
return OmemoDoubleRatchet(
|
||||
spk,
|
||||
null,
|
||||
@ -175,6 +187,7 @@ class OmemoDoubleRatchet {
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
ik,
|
||||
ad,
|
||||
{},
|
||||
);
|
||||
@ -199,6 +212,7 @@ class OmemoDoubleRatchet {
|
||||
'ns': ns,
|
||||
'nr': nr,
|
||||
'pn': pn,
|
||||
'ik_pub': base64.encode(await ik.getBytes()),
|
||||
'session_ad': base64.encode(sessionAd),
|
||||
'mkskipped': mkSkippedSerialised,
|
||||
};
|
||||
@ -300,9 +314,14 @@ class OmemoDoubleRatchet {
|
||||
final dhrMatch = dhr == null ? other.dhr == null : await dhr!.equals(other.dhr!);
|
||||
final ckrMatch = ckr == null ? other.ckr == null : listsEqual(ckr!, other.ckr!);
|
||||
final cksMatch = cks == null ? other.cks == null : listsEqual(cks!, other.cks!);
|
||||
|
||||
|
||||
// ignore: invalid_use_of_visible_for_testing_member
|
||||
return await dhs.equals(other.dhs) &&
|
||||
final dhsMatch = await dhs.equals(other.dhs);
|
||||
// ignore: invalid_use_of_visible_for_testing_member
|
||||
final ikMatch = await ik.equals(other.ik);
|
||||
|
||||
return dhsMatch &&
|
||||
ikMatch &&
|
||||
dhrMatch &&
|
||||
listsEqual(rk, other.rk) &&
|
||||
cksMatch &&
|
||||
|
19
lib/src/omemo/fingerprint.dart
Normal file
19
lib/src/omemo/fingerprint.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@immutable
|
||||
class DeviceFingerprint {
|
||||
|
||||
const DeviceFingerprint(this.deviceId, this.fingerprint);
|
||||
final String fingerprint;
|
||||
final int deviceId;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is DeviceFingerprint &&
|
||||
fingerprint == other.fingerprint &&
|
||||
deviceId == other.deviceId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => fingerprint.hashCode ^ deviceId.hashCode;
|
||||
}
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:hex/hex.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:omemo_dart/src/crypto.dart';
|
||||
import 'package:omemo_dart/src/double_ratchet/double_ratchet.dart';
|
||||
@ -11,6 +12,7 @@ import 'package:omemo_dart/src/helpers.dart';
|
||||
import 'package:omemo_dart/src/keys.dart';
|
||||
import 'package:omemo_dart/src/omemo/bundle.dart';
|
||||
import 'package:omemo_dart/src/omemo/device.dart';
|
||||
import 'package:omemo_dart/src/omemo/fingerprint.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';
|
||||
@ -19,7 +21,6 @@ import 'package:synchronized/synchronized.dart';
|
||||
|
||||
/// The info used for when encrypting the AES key for the actual payload.
|
||||
const omemoPayloadInfoString = 'OMEMO Payload';
|
||||
|
||||
@immutable
|
||||
class EncryptionResult {
|
||||
|
||||
@ -158,6 +159,7 @@ class OmemoSessionManager {
|
||||
);
|
||||
final ratchet = await OmemoDoubleRatchet.initiateNewSession(
|
||||
bundle.spk,
|
||||
bundle.ik,
|
||||
kexResult.sk,
|
||||
kexResult.ad,
|
||||
);
|
||||
@ -187,6 +189,7 @@ class OmemoSessionManager {
|
||||
);
|
||||
final ratchet = await OmemoDoubleRatchet.acceptNewSession(
|
||||
device.spk,
|
||||
OmemoPublicKey.fromBytes(kex.ik!, KeyPairType.ed25519),
|
||||
kexResult.sk,
|
||||
kexResult.ad,
|
||||
);
|
||||
@ -327,6 +330,29 @@ class OmemoSessionManager {
|
||||
return utf8.decode(plaintext);
|
||||
}
|
||||
|
||||
/// Returns the list of hex-encoded fingerprints we have for sessions with [jid].
|
||||
Future<List<DeviceFingerprint>> getHexFingerprintsForJid(String jid) async {
|
||||
final fingerprints = List<DeviceFingerprint>.empty(growable: true);
|
||||
|
||||
await _lock.synchronized(() async {
|
||||
// Get devices for jid
|
||||
final devices = _deviceMap[jid]!;
|
||||
|
||||
for (final deviceId in devices) {
|
||||
final ratchet = _ratchetMap[RatchetMapKey(jid, deviceId)]!;
|
||||
|
||||
fingerprints.add(
|
||||
DeviceFingerprint(
|
||||
deviceId,
|
||||
HEX.encode(await ratchet.ik.getBytes()),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return fingerprints;
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
OmemoDoubleRatchet getRatchet(String jid, int deviceId) => _ratchetMap[RatchetMapKey(jid, deviceId)]!;
|
||||
|
||||
|
@ -8,6 +8,7 @@ environment:
|
||||
dependencies:
|
||||
collection: ^1.16.0
|
||||
cryptography: ^2.0.5
|
||||
hex: ^0.2.0
|
||||
pinenacl: ^0.5.1
|
||||
synchronized: ^3.0.0+2
|
||||
|
||||
|
@ -79,11 +79,13 @@ void main() {
|
||||
// Build a session
|
||||
final alicesRatchet = await OmemoDoubleRatchet.initiateNewSession(
|
||||
spkBob.pk,
|
||||
ikBob.pk,
|
||||
resultAlice.sk,
|
||||
resultAlice.ad,
|
||||
);
|
||||
final bobsRatchet = await OmemoDoubleRatchet.acceptNewSession(
|
||||
spkBob,
|
||||
ikAlice.pk,
|
||||
resultBob.sk,
|
||||
resultBob.ad,
|
||||
);
|
||||
|
@ -132,5 +132,15 @@ void main() {
|
||||
bobResponseMessage.encryptedKeys,
|
||||
);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user