feat: Allow calculating fingerprints

This commit is contained in:
PapaTutuWawa 2022-08-08 14:26:25 +02:00
parent 70ab74b8dc
commit dd96e840d4
7 changed files with 86 additions and 8 deletions

View File

@ -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';

View File

@ -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 &&

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

View File

@ -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)]!;

View File

@ -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

View File

@ -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,
);

View File

@ -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);
});
}