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/keys.dart';
export 'src/omemo/bundle.dart'; export 'src/omemo/bundle.dart';
export 'src/omemo/device.dart'; export 'src/omemo/device.dart';
export 'src/omemo/fingerprint.dart';
export 'src/omemo/sessionmanager.dart'; export 'src/omemo/sessionmanager.dart';
export 'src/x3dh/x3dh.dart'; export 'src/x3dh/x3dh.dart';

View File

@ -65,8 +65,9 @@ class OmemoDoubleRatchet {
this.ns, // Ns this.ns, // Ns
this.nr, // Nr this.nr, // Nr
this.pn, // Pn this.pn, // Pn
this.ik,
this.sessionAd, this.sessionAd,
this.mkSkipped, this.mkSkipped, // MKSKIPPED
); );
factory OmemoDoubleRatchet.fromJson(Map<String, dynamic> data) { factory OmemoDoubleRatchet.fromJson(Map<String, dynamic> data) {
@ -81,6 +82,7 @@ class OmemoDoubleRatchet {
'ns': 0, 'ns': 0,
'nr': 0, 'nr': 0,
'pn': 0, 'pn': 0,
'ik_pub': 'base/64/encoded',
'session_ad': 'base/64/encoded', 'session_ad': 'base/64/encoded',
'mkskipped': [ 'mkskipped': [
{ {
@ -110,6 +112,10 @@ class OmemoDoubleRatchet {
data['ns']! as int, data['ns']! as int,
data['nr']! as int, data['nr']! as int,
data['pn']! as int, data['pn']! as int,
OmemoPublicKey.fromBytes(
base64.decode(data['ik_pub']! as String),
KeyPairType.ed25519,
),
base64.decode(data['session_ad']! as String), base64.decode(data['session_ad']! as String),
mkSkipped, mkSkipped,
); );
@ -135,14 +141,18 @@ class OmemoDoubleRatchet {
/// Previous sending chain number /// Previous sending chain number
int pn; 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 List<int> sessionAd;
final Map<SkippedKey, List<int>> mkSkipped; final Map<SkippedKey, List<int>> mkSkipped;
/// Create an OMEMO session using the Signed Pre Key [spk], the shared secret [sk] that /// 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 /// was obtained using a X3DH and the associated data [ad] that was also obtained through
/// a X3DH. /// a X3DH. [ik] refers to Bob's (the receiver's) IK public key.
static Future<OmemoDoubleRatchet> initiateNewSession(OmemoPublicKey spk, List<int> sk, List<int> ad) async { static Future<OmemoDoubleRatchet> initiateNewSession(OmemoPublicKey spk, OmemoPublicKey ik, List<int> sk, List<int> ad) async {
final dhs = await OmemoKeyPair.generateNewPair(KeyPairType.x25519); final dhs = await OmemoKeyPair.generateNewPair(KeyPairType.x25519);
final dhr = spk; final dhr = spk;
final rk = await kdfRk(sk, await omemoDH(dhs, dhr, 0)); final rk = await kdfRk(sk, await omemoDH(dhs, dhr, 0));
@ -157,6 +167,7 @@ class OmemoDoubleRatchet {
0, 0,
0, 0,
0, 0,
ik,
ad, ad,
{}, {},
); );
@ -164,8 +175,9 @@ class OmemoDoubleRatchet {
/// Create an OMEMO session that was not initiated by the caller using the used Signed /// 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 /// 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. /// the associated data [ad] that was also obtained through a X3DH. [ik] refers to
static Future<OmemoDoubleRatchet> acceptNewSession(OmemoKeyPair spk, List<int> sk, List<int> ad) async { /// 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( return OmemoDoubleRatchet(
spk, spk,
null, null,
@ -175,6 +187,7 @@ class OmemoDoubleRatchet {
0, 0,
0, 0,
0, 0,
ik,
ad, ad,
{}, {},
); );
@ -199,6 +212,7 @@ class OmemoDoubleRatchet {
'ns': ns, 'ns': ns,
'nr': nr, 'nr': nr,
'pn': pn, 'pn': pn,
'ik_pub': base64.encode(await ik.getBytes()),
'session_ad': base64.encode(sessionAd), 'session_ad': base64.encode(sessionAd),
'mkskipped': mkSkippedSerialised, 'mkskipped': mkSkippedSerialised,
}; };
@ -302,7 +316,12 @@ class OmemoDoubleRatchet {
final cksMatch = cks == null ? other.cks == null : listsEqual(cks!, other.cks!); final cksMatch = cks == null ? other.cks == null : listsEqual(cks!, other.cks!);
// ignore: invalid_use_of_visible_for_testing_member // 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 && dhrMatch &&
listsEqual(rk, other.rk) && listsEqual(rk, other.rk) &&
cksMatch && 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 'dart:convert';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:cryptography/cryptography.dart'; import 'package:cryptography/cryptography.dart';
import 'package:hex/hex.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/double_ratchet.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/keys.dart';
import 'package:omemo_dart/src/omemo/bundle.dart'; import 'package:omemo_dart/src/omemo/bundle.dart';
import 'package:omemo_dart/src/omemo/device.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_authenticated_message.dart';
import 'package:omemo_dart/src/protobuf/omemo_key_exchange.dart'; import 'package:omemo_dart/src/protobuf/omemo_key_exchange.dart';
import 'package:omemo_dart/src/protobuf/omemo_message.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. /// The info used for when encrypting the AES key for the actual payload.
const omemoPayloadInfoString = 'OMEMO Payload'; const omemoPayloadInfoString = 'OMEMO Payload';
@immutable @immutable
class EncryptionResult { class EncryptionResult {
@ -158,6 +159,7 @@ class OmemoSessionManager {
); );
final ratchet = await OmemoDoubleRatchet.initiateNewSession( final ratchet = await OmemoDoubleRatchet.initiateNewSession(
bundle.spk, bundle.spk,
bundle.ik,
kexResult.sk, kexResult.sk,
kexResult.ad, kexResult.ad,
); );
@ -187,6 +189,7 @@ class OmemoSessionManager {
); );
final ratchet = await OmemoDoubleRatchet.acceptNewSession( final ratchet = await OmemoDoubleRatchet.acceptNewSession(
device.spk, device.spk,
OmemoPublicKey.fromBytes(kex.ik!, KeyPairType.ed25519),
kexResult.sk, kexResult.sk,
kexResult.ad, kexResult.ad,
); );
@ -327,6 +330,29 @@ class OmemoSessionManager {
return utf8.decode(plaintext); 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 @visibleForTesting
OmemoDoubleRatchet getRatchet(String jid, int deviceId) => _ratchetMap[RatchetMapKey(jid, deviceId)]!; OmemoDoubleRatchet getRatchet(String jid, int deviceId) => _ratchetMap[RatchetMapKey(jid, deviceId)]!;

View File

@ -8,6 +8,7 @@ environment:
dependencies: dependencies:
collection: ^1.16.0 collection: ^1.16.0
cryptography: ^2.0.5 cryptography: ^2.0.5
hex: ^0.2.0
pinenacl: ^0.5.1 pinenacl: ^0.5.1
synchronized: ^3.0.0+2 synchronized: ^3.0.0+2

View File

@ -79,11 +79,13 @@ void main() {
// Build a session // Build a session
final alicesRatchet = await OmemoDoubleRatchet.initiateNewSession( final alicesRatchet = await OmemoDoubleRatchet.initiateNewSession(
spkBob.pk, spkBob.pk,
ikBob.pk,
resultAlice.sk, resultAlice.sk,
resultAlice.ad, resultAlice.ad,
); );
final bobsRatchet = await OmemoDoubleRatchet.acceptNewSession( final bobsRatchet = await OmemoDoubleRatchet.acceptNewSession(
spkBob, spkBob,
ikAlice.pk,
resultBob.sk, resultBob.sk,
resultBob.ad, resultBob.ad,
); );

View File

@ -132,5 +132,15 @@ void main() {
bobResponseMessage.encryptedKeys, bobResponseMessage.encryptedKeys,
); );
expect(bobResponseText, aliceReceivedMessage); 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);
}); });
} }