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/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';
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
@ -300,9 +314,14 @@ class OmemoDoubleRatchet {
|
|||||||
final dhrMatch = dhr == null ? other.dhr == null : await dhr!.equals(other.dhr!);
|
final dhrMatch = dhr == null ? other.dhr == null : await dhr!.equals(other.dhr!);
|
||||||
final ckrMatch = ckr == null ? other.ckr == null : listsEqual(ckr!, other.ckr!);
|
final ckrMatch = ckr == null ? other.ckr == null : listsEqual(ckr!, other.ckr!);
|
||||||
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 &&
|
||||||
|
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 '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)]!;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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,
|
||||||
);
|
);
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user