refactor: Migrate megolm to vodozemac

This commit is contained in:
Christian Kußowski 2025-05-30 11:47:53 +02:00
parent 31a32b0145
commit 98fcd683a6
No known key found for this signature in database
GPG Key ID: E067ECD60F1A0652
14 changed files with 155 additions and 120 deletions

View File

@ -227,7 +227,7 @@ class Encryption {
canRequestSession = false; canRequestSession = false;
// we can't have the key be an int, else json-serializing will fail, thus we need it to be a string // we can't have the key be an int, else json-serializing will fail, thus we need it to be a string
final messageIndexKey = 'key-${decryptResult.message_index}'; final messageIndexKey = 'key-${decryptResult.messageIndex}';
final messageIndexValue = final messageIndexValue =
'${event.eventId}|${event.originServerTs.millisecondsSinceEpoch}'; '${event.eventId}|${event.originServerTs.millisecondsSinceEpoch}';
final haveIndex = final haveIndex =
@ -255,13 +255,14 @@ class Encryption {
.onError((e, _) => Logs().e('Ignoring error for updating indexes')); .onError((e, _) => Logs().e('Ignoring error for updating indexes'));
} }
decryptedPayload = json.decode(decryptResult.plaintext); decryptedPayload = json.decode(decryptResult.plaintext);
} catch (exception) { } catch (exception, stackTrace) {
Logs().w('Could not decrypt event', exception, stackTrace);
// alright, if this was actually by our own outbound group session, we might as well clear it // alright, if this was actually by our own outbound group session, we might as well clear it
if (exception.toString() != DecryptException.unknownSession && if (exception.toString() != DecryptException.unknownSession &&
(keyManager (keyManager
.getOutboundGroupSession(event.room.id) .getOutboundGroupSession(event.room.id)
?.outboundGroupSession ?.outboundGroupSession
?.session_id() ?? ?.sessionId ??
'') == '') ==
content.sessionId) { content.sessionId) {
runInRoot( runInRoot(
@ -409,7 +410,7 @@ class Encryption {
// they're deprecated. Just left here for compatibility // they're deprecated. Just left here for compatibility
'device_id': client.deviceID, 'device_id': client.deviceID,
'sender_key': identityKey, 'sender_key': identityKey,
'session_id': sess.outboundGroupSession!.session_id(), 'session_id': sess.outboundGroupSession!.sessionId,
if (mRelatesTo != null) 'm.relates_to': mRelatesTo, if (mRelatesTo != null) 'm.relates_to': mRelatesTo,
}; };
await keyManager.storeOutboundGroupSession(roomId, sess); await keyManager.storeOutboundGroupSession(roomId, sess);

View File

@ -20,12 +20,12 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:olm/olm.dart' as olm;
import 'package:vodozemac/vodozemac.dart' as vod; import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/encryption.dart'; import 'package:matrix/encryption/encryption.dart';
import 'package:matrix/encryption/utils/base64_unpadded.dart'; import 'package:matrix/encryption/utils/base64_unpadded.dart';
import 'package:matrix/encryption/utils/outbound_group_session.dart'; import 'package:matrix/encryption/utils/outbound_group_session.dart';
import 'package:matrix/encryption/utils/pickle_key.dart';
import 'package:matrix/encryption/utils/session_key.dart'; import 'package:matrix/encryption/utils/session_key.dart';
import 'package:matrix/encryption/utils/stored_inbound_group_session.dart'; import 'package:matrix/encryption/utils/stored_inbound_group_session.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
@ -118,16 +118,15 @@ class KeyManager {
if (content['algorithm'] != AlgorithmTypes.megolmV1AesSha2) { if (content['algorithm'] != AlgorithmTypes.megolmV1AesSha2) {
return; return;
} }
late olm.InboundGroupSession inboundGroupSession; late vod.InboundGroupSession inboundGroupSession;
try { try {
inboundGroupSession = olm.InboundGroupSession();
if (forwarded) { if (forwarded) {
inboundGroupSession.import_session(content['session_key']); inboundGroupSession =
vod.InboundGroupSession.import(content['session_key']);
} else { } else {
inboundGroupSession.create(content['session_key']); inboundGroupSession = vod.InboundGroupSession(content['session_key']);
} }
} catch (e, s) { } catch (e, s) {
inboundGroupSession.free();
Logs().e('[LibOlm] Could not create new InboundGroupSession', e, s); Logs().e('[LibOlm] Could not create new InboundGroupSession', e, s);
return Future.value(); return Future.value();
} }
@ -142,19 +141,16 @@ class KeyManager {
senderClaimedKeys: senderClaimedKeys_, senderClaimedKeys: senderClaimedKeys_,
allowedAtIndex: allowedAtIndex_, allowedAtIndex: allowedAtIndex_,
); );
final oldFirstIndex = final oldFirstIndex = oldSession?.inboundGroupSession?.firstKnownIndex ?? 0;
oldSession?.inboundGroupSession?.first_known_index() ?? 0; final newFirstIndex = newSession.inboundGroupSession!.firstKnownIndex;
final newFirstIndex = newSession.inboundGroupSession!.first_known_index();
if (oldSession == null || if (oldSession == null ||
newFirstIndex < oldFirstIndex || newFirstIndex < oldFirstIndex ||
(oldFirstIndex == newFirstIndex && (oldFirstIndex == newFirstIndex &&
newSession.forwardingCurve25519KeyChain.length < newSession.forwardingCurve25519KeyChain.length <
oldSession.forwardingCurve25519KeyChain.length)) { oldSession.forwardingCurve25519KeyChain.length)) {
// use new session // use new session
oldSession?.dispose();
} else { } else {
// we are gonna keep our old session // we are gonna keep our old session
newSession.dispose();
return; return;
} }
@ -169,7 +165,7 @@ class KeyManager {
.storeInboundGroupSession( .storeInboundGroupSession(
roomId, roomId,
sessionId, sessionId,
inboundGroupSession.pickle(userId), inboundGroupSession.toPickleEncrypted(userId.toPickleKey()),
json.encode(content), json.encode(content),
json.encode({}), json.encode({}),
json.encode(allowedAtIndex_), json.encode(allowedAtIndex_),
@ -344,7 +340,7 @@ class KeyManager {
final inboundSess = await loadInboundGroupSession( final inboundSess = await loadInboundGroupSession(
room.id, room.id,
sess.outboundGroupSession!.session_id(), sess.outboundGroupSession!.sessionId,
); );
if (inboundSess == null) { if (inboundSess == null) {
Logs().w('No inbound megolm session found for outbound session!'); Logs().w('No inbound megolm session found for outbound session!');
@ -436,8 +432,8 @@ class KeyManager {
final rawSession = <String, dynamic>{ final rawSession = <String, dynamic>{
'algorithm': AlgorithmTypes.megolmV1AesSha2, 'algorithm': AlgorithmTypes.megolmV1AesSha2,
'room_id': room.id, 'room_id': room.id,
'session_id': sess.outboundGroupSession!.session_id(), 'session_id': sess.outboundGroupSession!.sessionId,
'session_key': sess.outboundGroupSession!.session_key(), 'session_key': sess.outboundGroupSession!.sessionKey,
}; };
try { try {
devicesToReceive.removeWhere((k) => !k.encryptToDevice); devicesToReceive.removeWhere((k) => !k.encryptToDevice);
@ -449,16 +445,16 @@ class KeyManager {
.containsKey(device.curve25519Key) || .containsKey(device.curve25519Key) ||
inboundSess.allowedAtIndex[device.userId]![ inboundSess.allowedAtIndex[device.userId]![
device.curve25519Key]! > device.curve25519Key]! >
sess.outboundGroupSession!.message_index()) { sess.outboundGroupSession!.messageIndex) {
inboundSess inboundSess
.allowedAtIndex[device.userId]![device.curve25519Key!] = .allowedAtIndex[device.userId]![device.curve25519Key!] =
sess.outboundGroupSession!.message_index(); sess.outboundGroupSession!.messageIndex;
} }
} }
await client.database.updateInboundGroupSessionAllowedAtIndex( await client.database.updateInboundGroupSessionAllowedAtIndex(
json.encode(inboundSess!.allowedAtIndex), json.encode(inboundSess!.allowedAtIndex),
room.id, room.id,
sess.outboundGroupSession!.session_id(), sess.outboundGroupSession!.sessionId,
); );
// send out the key // send out the key
await client.sendToDeviceEncryptedChunked( await client.sendToDeviceEncryptedChunked(
@ -477,7 +473,6 @@ class KeyManager {
return false; return false;
} }
} }
sess.dispose();
_outboundGroupSessions.remove(roomId); _outboundGroupSessions.remove(roomId);
await client.database.removeOutboundGroupSession(roomId); await client.database.removeOutboundGroupSession(roomId);
return true; return true;
@ -492,7 +487,7 @@ class KeyManager {
if (userID == null) return; if (userID == null) return;
await client.database.storeOutboundGroupSession( await client.database.storeOutboundGroupSession(
roomId, roomId,
sess.outboundGroupSession!.pickle(userID), sess.outboundGroupSession!.toPickleEncrypted(userID.toPickleKey()),
json.encode(sess.devices), json.encode(sess.devices),
sess.creationTime.millisecondsSinceEpoch, sess.creationTime.millisecondsSinceEpoch,
); );
@ -553,19 +548,13 @@ class KeyManager {
final deviceKeys = await room.getUserDeviceKeys(); final deviceKeys = await room.getUserDeviceKeys();
final deviceKeyIds = _getDeviceKeyIdMap(deviceKeys); final deviceKeyIds = _getDeviceKeyIdMap(deviceKeys);
deviceKeys.removeWhere((k) => !k.encryptToDevice); deviceKeys.removeWhere((k) => !k.encryptToDevice);
final outboundGroupSession = olm.OutboundGroupSession(); final outboundGroupSession = vod.GroupSession();
try {
outboundGroupSession.create();
} catch (e, s) {
outboundGroupSession.free();
Logs().e('[LibOlm] Unable to create new outboundGroupSession', e, s);
rethrow;
}
final rawSession = <String, dynamic>{ final rawSession = <String, dynamic>{
'algorithm': AlgorithmTypes.megolmV1AesSha2, 'algorithm': AlgorithmTypes.megolmV1AesSha2,
'room_id': room.id, 'room_id': room.id,
'session_id': outboundGroupSession.session_id(), 'session_id': outboundGroupSession.sessionId,
'session_key': outboundGroupSession.session_key(), 'session_key': outboundGroupSession.sessionKey,
}; };
final allowedAtIndex = <String, Map<String, int>>{}; final allowedAtIndex = <String, Map<String, int>>{};
for (final device in deviceKeys) { for (final device in deviceKeys) {
@ -575,7 +564,7 @@ class KeyManager {
} }
allowedAtIndex[device.userId] ??= <String, int>{}; allowedAtIndex[device.userId] ??= <String, int>{};
allowedAtIndex[device.userId]![device.curve25519Key!] = allowedAtIndex[device.userId]![device.curve25519Key!] =
outboundGroupSession.message_index(); outboundGroupSession.messageIndex;
} }
await setInboundGroupSession( await setInboundGroupSession(
roomId, roomId,
@ -604,7 +593,6 @@ class KeyManager {
e, e,
s, s,
); );
sess.dispose();
rethrow; rethrow;
} }
return sess; return sess;
@ -1163,14 +1151,6 @@ class KeyManager {
void dispose() { void dispose() {
// ignore: discarded_futures // ignore: discarded_futures
_uploadKeysOnSync?.cancel(); _uploadKeysOnSync?.cancel();
for (final sess in _outboundGroupSessions.values) {
sess.dispose();
}
for (final entries in _inboundGroupSessions.values) {
for (final sess in entries.values) {
sess.dispose();
}
}
} }
} }
@ -1233,8 +1213,8 @@ class RoomKeyRequest extends ToDeviceEvent {
(session.forwardingCurve25519KeyChain.isEmpty (session.forwardingCurve25519KeyChain.isEmpty
? keyManager.encryption.fingerprintKey ? keyManager.encryption.fingerprintKey
: null); : null);
message['session_key'] = session.inboundGroupSession!.export_session( message['session_key'] = session.inboundGroupSession!.exportAt(
index ?? session.inboundGroupSession!.first_known_index(), index ?? session.inboundGroupSession!.firstKnownIndex,
); );
// send the actual reply of the key back to the requester // send the actual reply of the key back to the requester
await keyManager.client.sendToDeviceEncrypted( await keyManager.client.sendToDeviceEncrypted(
@ -1269,8 +1249,7 @@ RoomKeys generateUploadKeysImplementation(GenerateUploadKeysArgs args) {
'forwarding_curve25519_key_chain': sess.forwardingCurve25519KeyChain, 'forwarding_curve25519_key_chain': sess.forwardingCurve25519KeyChain,
'sender_key': sess.senderKey, 'sender_key': sess.senderKey,
'sender_claimed_keys': sess.senderClaimedKeys, 'sender_claimed_keys': sess.senderClaimedKeys,
'session_key': sess.inboundGroupSession! 'session_key': sess.inboundGroupSession!.exportAtFirstKnownIndex(),
.export_session(sess.inboundGroupSession!.first_known_index()),
}; };
// encrypt the content // encrypt the content
final encrypted = enc.encrypt(json.encode(payload)); final encrypted = enc.encrypt(json.encode(payload));
@ -1278,7 +1257,7 @@ RoomKeys generateUploadKeysImplementation(GenerateUploadKeysArgs args) {
//final device = args.client.getUserDeviceKeysByCurve25519Key(sess.senderKey); //final device = args.client.getUserDeviceKeysByCurve25519Key(sess.senderKey);
// aaaand finally add the session key to our payload // aaaand finally add the session key to our payload
roomKeyBackup.sessions[sess.sessionId] = KeyBackupData( roomKeyBackup.sessions[sess.sessionId] = KeyBackupData(
firstMessageIndex: sess.inboundGroupSession!.first_known_index(), firstMessageIndex: sess.inboundGroupSession!.firstKnownIndex,
forwardedCount: sess.forwardingCurve25519KeyChain.length, forwardedCount: sess.forwardingCurve25519KeyChain.length,
isVerified: dbSession.verified, //device?.verified ?? false, isVerified: dbSession.verified, //device?.verified ?? false,
sessionData: { sessionData: {

View File

@ -18,8 +18,9 @@
import 'dart:convert'; import 'dart:convert';
import 'package:olm/olm.dart' as olm; import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/utils/pickle_key.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
class OutboundGroupSession { class OutboundGroupSession {
@ -30,8 +31,8 @@ class OutboundGroupSession {
Map<String, Map<String, bool>> devices = {}; Map<String, Map<String, bool>> devices = {};
// Default to a date, that would get this session rotated in any case to make handling easier // Default to a date, that would get this session rotated in any case to make handling easier
DateTime creationTime = DateTime.fromMillisecondsSinceEpoch(0); DateTime creationTime = DateTime.fromMillisecondsSinceEpoch(0);
olm.OutboundGroupSession? outboundGroupSession; vod.GroupSession? outboundGroupSession;
int? get sentMessages => outboundGroupSession?.message_index(); int? get sentMessages => outboundGroupSession?.messageIndex;
bool get isValid => outboundGroupSession != null; bool get isValid => outboundGroupSession != null;
final String key; final String key;
@ -54,19 +55,24 @@ class OutboundGroupSession {
); );
return; return;
} }
outboundGroupSession = olm.OutboundGroupSession();
try {
outboundGroupSession!.unpickle(key, dbEntry['pickle']);
creationTime = creationTime =
DateTime.fromMillisecondsSinceEpoch(dbEntry['creation_time']); DateTime.fromMillisecondsSinceEpoch(dbEntry['creation_time']);
try {
outboundGroupSession = vod.GroupSession.fromPickleEncrypted(
pickleKey: key.toPickleKey(),
pickle: dbEntry['pickle'],
);
} catch (e, s) { } catch (e, s) {
dispose(); try {
outboundGroupSession = vod.GroupSession.fromOlmPickleEncrypted(
pickleKey: utf8.encode(key),
pickle: dbEntry['pickle'],
);
} catch (_) {
Logs().e('[LibOlm] Unable to unpickle outboundGroupSession', e, s); Logs().e('[LibOlm] Unable to unpickle outboundGroupSession', e, s);
} }
} }
void dispose() {
outboundGroupSession?.free();
outboundGroupSession = null;
} }
} }

View File

@ -0,0 +1,15 @@
import 'dart:typed_data';
extension PickleKeyStringExtension on String {
Uint8List toPickleKey() {
final bytes = Uint8List.fromList(codeUnits);
final missing = 32 - bytes.length;
if (missing > 0) {
return Uint8List.fromList([
...bytes,
...List.filled(missing, 0),
]);
}
return Uint8List.fromList(bytes.getRange(0, 32).toList());
}
}

View File

@ -16,8 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import 'package:olm/olm.dart' as olm; import 'dart:convert';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/utils/pickle_key.dart';
import 'package:matrix/encryption/utils/stored_inbound_group_session.dart'; import 'package:matrix/encryption/utils/stored_inbound_group_session.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
@ -33,7 +36,7 @@ class SessionKey {
Map<String, Map<String, int>> allowedAtIndex; Map<String, Map<String, int>> allowedAtIndex;
/// Underlying olm [InboundGroupSession] object /// Underlying olm [InboundGroupSession] object
olm.InboundGroupSession? inboundGroupSession; vod.InboundGroupSession? inboundGroupSession;
/// Key for libolm pickle / unpickle /// Key for libolm pickle / unpickle
final String key; final String key;
@ -81,8 +84,7 @@ class SessionKey {
.catchMap((k, v) => MapEntry(k, Map<String, int>.from(v))), .catchMap((k, v) => MapEntry(k, Map<String, int>.from(v))),
roomId = dbEntry.roomId, roomId = dbEntry.roomId,
sessionId = dbEntry.sessionId, sessionId = dbEntry.sessionId,
senderKey = dbEntry.senderKey, senderKey = dbEntry.senderKey {
inboundGroupSession = olm.InboundGroupSession() {
final parsedSenderClaimedKeys = final parsedSenderClaimedKeys =
Event.getMapFromPayload(dbEntry.senderClaimedKeys) Event.getMapFromPayload(dbEntry.senderClaimedKeys)
.catchMap((k, v) => MapEntry<String, String>(k, v)); .catchMap((k, v) => MapEntry<String, String>(k, v));
@ -99,15 +101,20 @@ class SessionKey {
: <String, String>{})); : <String, String>{}));
try { try {
inboundGroupSession!.unpickle(key, dbEntry.pickle); inboundGroupSession = vod.InboundGroupSession.fromPickleEncrypted(
pickle: dbEntry.pickle,
pickleKey: key.toPickleKey(),
);
} catch (e, s) { } catch (e, s) {
dispose(); try {
inboundGroupSession = vod.InboundGroupSession.fromOlmPickleEncrypted(
pickle: dbEntry.pickle,
pickleKey: utf8.encode(key),
);
} catch (_) {
Logs().e('[LibOlm] Unable to unpickle inboundGroupSession', e, s); Logs().e('[LibOlm] Unable to unpickle inboundGroupSession', e, s);
rethrow;
} }
} }
void dispose() {
inboundGroupSession?.free();
inboundGroupSession = null;
} }
} }

View File

@ -68,15 +68,8 @@ void main() {
group('client mem', tags: 'olm', () { group('client mem', tags: 'olm', () {
late Client matrix; late Client matrix;
Future? vodInit;
/// Check if all Elements get created /// Check if all Elements get created
setUp(() async { setUp(() async {
vodInit ??= vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await vodInit;
matrix = await getClient(); matrix = await getClient();
}); });

View File

@ -20,6 +20,7 @@ import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'fake_client.dart'; import 'fake_client.dart';
@ -47,6 +48,10 @@ void main() {
} }
test('setupClient', () async { test('setupClient', () async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
client = await getClient(); client = await getClient();
room = Room(id: '!1234:fakeServer.notExisting', client: client); room = Room(id: '!1234:fakeServer.notExisting', client: client);
room.setState( room.setState(

View File

@ -115,7 +115,7 @@ void main() {
); );
var inbound = client.encryption!.keyManager.getInboundGroupSession( var inbound = client.encryption!.keyManager.getInboundGroupSession(
roomId, roomId,
sess.outboundGroupSession!.session_id(), sess.outboundGroupSession!.sessionId,
); );
expect(inbound != null, true); expect(inbound != null, true);
expect( expect(
@ -157,7 +157,7 @@ void main() {
// lazy-create if it would rotate // lazy-create if it would rotate
sess = await client.encryption!.keyManager sess = await client.encryption!.keyManager
.createOutboundGroupSession(roomId); .createOutboundGroupSession(roomId);
final oldSessKey = sess.outboundGroupSession!.session_key(); final oldSessKey = sess.outboundGroupSession!.sessionKey;
client.userDeviceKeys['@alice:example.com']!.deviceKeys['JLAFKJWSCS']! client.userDeviceKeys['@alice:example.com']!.deviceKeys['JLAFKJWSCS']!
.blocked = true; .blocked = true;
await client.encryption!.keyManager.prepareOutboundGroupSession(roomId); await client.encryption!.keyManager.prepareOutboundGroupSession(roomId);
@ -169,7 +169,7 @@ void main() {
client.encryption!.keyManager client.encryption!.keyManager
.getOutboundGroupSession(roomId)! .getOutboundGroupSession(roomId)!
.outboundGroupSession! .outboundGroupSession!
.session_key() != .sessionKey !=
oldSessKey, oldSessKey,
true, true,
); );
@ -241,7 +241,7 @@ void main() {
); );
inbound = client.encryption!.keyManager.getInboundGroupSession( inbound = client.encryption!.keyManager.getInboundGroupSession(
roomId, roomId,
sess.outboundGroupSession!.session_id(), sess.outboundGroupSession!.sessionId,
); );
expect( expect(
inbound!.allowedAtIndex['@alice:example.com'] inbound!.allowedAtIndex['@alice:example.com']
@ -358,13 +358,13 @@ void main() {
}); });
test('setInboundGroupSession', () async { test('setInboundGroupSession', () async {
final session = olm.OutboundGroupSession(); final session = vod.GroupSession();
session.create();
final inbound = olm.InboundGroupSession(); final inbound = vod.InboundGroupSession(session.sessionKey);
inbound.create(session.session_key());
final senderKey = client.identityKey; final senderKey = client.identityKey;
final roomId = '!someroom:example.org'; final roomId = '!someroom:example.org';
final sessionId = inbound.session_id(); final sessionId = inbound.sessionId;
final room = Room(id: roomId, client: client); final room = Room(id: roomId, client: client);
final nextSyncUpdateFuture = client.onSync.stream final nextSyncUpdateFuture = client.onSync.stream
.firstWhere((update) => update.rooms != null) .firstWhere((update) => update.rooms != null)
@ -413,7 +413,7 @@ void main() {
'room_id': roomId, 'room_id': roomId,
'forwarding_curve25519_key_chain': [client.identityKey], 'forwarding_curve25519_key_chain': [client.identityKey],
'session_id': sessionId, 'session_id': sessionId,
'session_key': inbound.export_session(1), 'session_key': inbound.exportAt(1),
'sender_key': senderKey, 'sender_key': senderKey,
'sender_claimed_ed25519_key': client.fingerprintKey, 'sender_claimed_ed25519_key': client.fingerprintKey,
}; };
@ -428,7 +428,7 @@ void main() {
client.encryption!.keyManager client.encryption!.keyManager
.getInboundGroupSession(roomId, sessionId) .getInboundGroupSession(roomId, sessionId)
?.inboundGroupSession ?.inboundGroupSession
?.first_known_index(), ?.firstKnownIndex,
1, 1,
); );
expect( expect(
@ -445,7 +445,7 @@ void main() {
'room_id': roomId, 'room_id': roomId,
'forwarding_curve25519_key_chain': [client.identityKey], 'forwarding_curve25519_key_chain': [client.identityKey],
'session_id': sessionId, 'session_id': sessionId,
'session_key': inbound.export_session(2), 'session_key': inbound.exportAt(2),
'sender_key': senderKey, 'sender_key': senderKey,
'sender_claimed_ed25519_key': client.fingerprintKey, 'sender_claimed_ed25519_key': client.fingerprintKey,
}; };
@ -460,7 +460,7 @@ void main() {
client.encryption!.keyManager client.encryption!.keyManager
.getInboundGroupSession(roomId, sessionId) .getInboundGroupSession(roomId, sessionId)
?.inboundGroupSession ?.inboundGroupSession
?.first_known_index(), ?.firstKnownIndex,
1, 1,
); );
expect( expect(
@ -477,7 +477,7 @@ void main() {
'room_id': roomId, 'room_id': roomId,
'forwarding_curve25519_key_chain': [client.identityKey], 'forwarding_curve25519_key_chain': [client.identityKey],
'session_id': sessionId, 'session_id': sessionId,
'session_key': inbound.export_session(0), 'session_key': inbound.exportAt(0),
'sender_key': senderKey, 'sender_key': senderKey,
'sender_claimed_ed25519_key': client.fingerprintKey, 'sender_claimed_ed25519_key': client.fingerprintKey,
}; };
@ -492,7 +492,7 @@ void main() {
client.encryption!.keyManager client.encryption!.keyManager
.getInboundGroupSession(roomId, sessionId) .getInboundGroupSession(roomId, sessionId)
?.inboundGroupSession ?.inboundGroupSession
?.first_known_index(), ?.firstKnownIndex,
0, 0,
); );
expect( expect(
@ -509,7 +509,7 @@ void main() {
'room_id': roomId, 'room_id': roomId,
'forwarding_curve25519_key_chain': [client.identityKey, 'beep'], 'forwarding_curve25519_key_chain': [client.identityKey, 'beep'],
'session_id': sessionId, 'session_id': sessionId,
'session_key': inbound.export_session(0), 'session_key': inbound.exportAt(0),
'sender_key': senderKey, 'sender_key': senderKey,
'sender_claimed_ed25519_key': client.fingerprintKey, 'sender_claimed_ed25519_key': client.fingerprintKey,
}; };
@ -524,7 +524,7 @@ void main() {
client.encryption!.keyManager client.encryption!.keyManager
.getInboundGroupSession(roomId, sessionId) .getInboundGroupSession(roomId, sessionId)
?.inboundGroupSession ?.inboundGroupSession
?.first_known_index(), ?.firstKnownIndex,
0, 0,
); );
expect( expect(
@ -541,7 +541,7 @@ void main() {
'room_id': roomId, 'room_id': roomId,
'forwarding_curve25519_key_chain': [], 'forwarding_curve25519_key_chain': [],
'session_id': sessionId, 'session_id': sessionId,
'session_key': inbound.export_session(0), 'session_key': inbound.exportAt(0),
'sender_key': senderKey, 'sender_key': senderKey,
'sender_claimed_ed25519_key': client.fingerprintKey, 'sender_claimed_ed25519_key': client.fingerprintKey,
}; };
@ -556,7 +556,7 @@ void main() {
client.encryption!.keyManager client.encryption!.keyManager
.getInboundGroupSession(roomId, sessionId) .getInboundGroupSession(roomId, sessionId)
?.inboundGroupSession ?.inboundGroupSession
?.first_known_index(), ?.firstKnownIndex,
0, 0,
); );
expect( expect(
@ -575,9 +575,6 @@ void main() {
// decrypted last event // decrypted last event
final syncUpdate = await nextSyncUpdateFuture; final syncUpdate = await nextSyncUpdateFuture;
expect(syncUpdate.rooms?.join?.containsKey(room.id), true); expect(syncUpdate.rooms?.join?.containsKey(room.id), true);
inbound.free();
session.free();
}); });
test('Reused deviceID attack', () async { test('Reused deviceID attack', () async {

View File

@ -314,8 +314,7 @@ void main() {
final session = (await matrix.encryption!.keyManager final session = (await matrix.encryption!.keyManager
.loadInboundGroupSession(requestRoom.id, validSessionId))!; .loadInboundGroupSession(requestRoom.id, validSessionId))!;
final sessionKey = session.inboundGroupSession! final sessionKey = session.inboundGroupSession!.exportAtFirstKnownIndex();
.export_session(session.inboundGroupSession!.first_known_index());
matrix.encryption!.keyManager.clearInboundGroupSessions(); matrix.encryption!.keyManager.clearInboundGroupSessions();
var event = ToDeviceEvent( var event = ToDeviceEvent(
sender: '@alice:example.com', sender: '@alice:example.com',

View File

@ -17,10 +17,12 @@
*/ */
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:matrix/encryption/utils/base64_unpadded.dart'; import 'package:matrix/encryption/utils/base64_unpadded.dart';
import 'package:matrix/encryption/utils/pickle_key.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
void main() { void main() {
@ -84,4 +86,27 @@ void main() {
); );
}); });
}); });
group('toPickleKey', () {
test('toPickleKey', () {
const shortKey = 'abcd';
var pickleKey = shortKey.toPickleKey();
expect(pickleKey.length, 32, reason: 'Pickle key should be 32 bytes');
expect(
shortKey,
String.fromCharCodes(pickleKey.take(4)),
reason: 'Pickle key should match the first 32 bytes of the input',
);
const longKey =
'abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890';
pickleKey = longKey.toPickleKey();
expect(pickleKey.length, 32, reason: 'Pickle key should be 32 bytes');
expect(
pickleKey,
Uint8List.fromList(longKey.codeUnits.take(32).toList()),
reason: 'Pickle key should match the first 32 bytes of the input',
);
});
});
} }

View File

@ -17,6 +17,7 @@
*/ */
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'fake_database.dart'; import 'fake_database.dart';
const ssssPassphrase = 'nae7ahDiequ7ohniufah3ieS2je1thohX4xeeka7aixohsho9O'; const ssssPassphrase = 'nae7ahDiequ7ohniufah3ieS2je1thohX4xeeka7aixohsho9O';
@ -26,11 +27,22 @@ const ssssKey = 'EsT9 RzbW VhPW yqNp cC7j ViiW 5TZB LuY4 ryyv 9guN Ysmr WDPH';
const pickledOlmAccount = const pickledOlmAccount =
'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuyg3RxViwdNxs3718fyAqQ/VSwbXsY0Nl+qQbF+nlVGHenGqk5SuNl1P6e1PzZxcR0IfXA94Xij1Ob5gDv5YH4UCn9wRMG0abZsQP0YzpDM0FLaHSCyo9i5JD/vMlhH+nZWrgAzPPCTNGYewNV8/h3c+VyJh8ZTx/fVi6Yq46Fv+27Ga2ETRZ3Qn+Oyx6dLBjnBZ9iUvIhqpe2XqaGA1PopOz8iDnaZitw'; 'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuyg3RxViwdNxs3718fyAqQ/VSwbXsY0Nl+qQbF+nlVGHenGqk5SuNl1P6e1PzZxcR0IfXA94Xij1Ob5gDv5YH4UCn9wRMG0abZsQP0YzpDM0FLaHSCyo9i5JD/vMlhH+nZWrgAzPPCTNGYewNV8/h3c+VyJh8ZTx/fVi6Yq46Fv+27Ga2ETRZ3Qn+Oyx6dLBjnBZ9iUvIhqpe2XqaGA1PopOz8iDnaZitw';
Future? vodInit;
/// only use `path` if you explicitly if you need a db on path instead of in mem /// only use `path` if you explicitly if you need a db on path instead of in mem
Future<Client> getClient({ Future<Client> getClient({
Duration sendTimelineEventTimeout = const Duration(minutes: 1), Duration sendTimelineEventTimeout = const Duration(minutes: 1),
String? databasePath, String? databasePath,
}) async { }) async {
try {
vodInit ??= vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await vodInit;
} catch (_) {
Logs().d('Encryption via Vodozemac not enabled');
}
final client = Client( final client = Client(
logLevel: Level.verbose, logLevel: Level.verbose,
'testclient', 'testclient',

View File

@ -138,7 +138,7 @@ void main() async {
final inboundSession = final inboundSession =
client.encryption!.keyManager.getInboundGroupSession( client.encryption!.keyManager.getInboundGroupSession(
roomid, roomid,
outboundSession!.outboundGroupSession!.session_id(), outboundSession!.outboundGroupSession!.sessionId,
)!; )!;
// ensure encryption is "enabled" // ensure encryption is "enabled"

View File

@ -675,13 +675,9 @@ void main() {
test( test(
'calcEncryptionHealthState', 'calcEncryptionHealthState',
() async { () async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
expect( expect(
await room.calcEncryptionHealthState(), await room.calcEncryptionHealthState(),
EncryptionHealthState.allVerified, EncryptionHealthState.unverifiedDevices,
); );
}, },
tags: 'olm', tags: 'olm',

View File

@ -234,7 +234,7 @@ void main() => group(
var currentSessionIdA = room.client.encryption!.keyManager var currentSessionIdA = room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)! .getOutboundGroupSession(room.id)!
.outboundGroupSession! .outboundGroupSession!
.session_id(); .sessionId;
/*expect(room.client.encryption.keyManager /*expect(room.client.encryption.keyManager
.getInboundGroupSession(room.id, currentSessionIdA, '') != .getInboundGroupSession(room.id, currentSessionIdA, '') !=
null);*/ null);*/
@ -289,7 +289,7 @@ void main() => group(
room.client.encryption!.keyManager room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)! .getOutboundGroupSession(room.id)!
.outboundGroupSession! .outboundGroupSession!
.session_id(), .sessionId,
currentSessionIdA, currentSessionIdA,
); );
/*expect(room.client.encryption.keyManager /*expect(room.client.encryption.keyManager
@ -320,7 +320,7 @@ void main() => group(
room.client.encryption!.keyManager room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)! .getOutboundGroupSession(room.id)!
.outboundGroupSession! .outboundGroupSession!
.session_id(), .sessionId,
currentSessionIdA, currentSessionIdA,
); );
final inviteRoomOutboundGroupSession = inviteRoom final inviteRoomOutboundGroupSession = inviteRoom
@ -330,12 +330,12 @@ void main() => group(
expect(inviteRoomOutboundGroupSession.isValid, isTrue); expect(inviteRoomOutboundGroupSession.isValid, isTrue);
/*expect(inviteRoom.client.encryption.keyManager.getInboundGroupSession( /*expect(inviteRoom.client.encryption.keyManager.getInboundGroupSession(
inviteRoom.id, inviteRoom.id,
inviteRoomOutboundGroupSession.outboundGroupSession.session_id(), inviteRoomOutboundGroupSession.outboundGroupSession.sessionId,
'') != '') !=
null); null);
expect(room.client.encryption.keyManager.getInboundGroupSession( expect(room.client.encryption.keyManager.getInboundGroupSession(
room.id, room.id,
inviteRoomOutboundGroupSession.outboundGroupSession.session_id(), inviteRoomOutboundGroupSession.outboundGroupSession.sessionId,
'') != '') !=
null);*/ null);*/
expect(inviteRoom.lastEvent!.body, testMessage3); expect(inviteRoom.lastEvent!.body, testMessage3);
@ -397,7 +397,7 @@ void main() => group(
room.client.encryption!.keyManager room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)! .getOutboundGroupSession(room.id)!
.outboundGroupSession! .outboundGroupSession!
.session_id(), .sessionId,
currentSessionIdA, currentSessionIdA,
); );
/*expect(inviteRoom.client.encryption.keyManager /*expect(inviteRoom.client.encryption.keyManager
@ -445,14 +445,14 @@ void main() => group(
room.client.encryption!.keyManager room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)! .getOutboundGroupSession(room.id)!
.outboundGroupSession! .outboundGroupSession!
.session_id(), .sessionId,
isNot(currentSessionIdA), isNot(currentSessionIdA),
); );
} }
currentSessionIdA = room.client.encryption!.keyManager currentSessionIdA = room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)! .getOutboundGroupSession(room.id)!
.outboundGroupSession! .outboundGroupSession!
.session_id(); .sessionId;
/*expect(inviteRoom.client.encryption.keyManager /*expect(inviteRoom.client.encryption.keyManager
.getInboundGroupSession(inviteRoom.id, currentSessionIdA, '') != .getInboundGroupSession(inviteRoom.id, currentSessionIdA, '') !=
null);*/ null);*/