Merge branch 'soru/autoreply-more-key-requests' into 'main'

feat: Auto-Share megolm sessions with other users we know for a fact are...

Closes #127

See merge request famedly/famedlysdk!570
This commit is contained in:
Sorunome 2020-12-21 14:08:17 +00:00
commit dfd88277b9
7 changed files with 231 additions and 36 deletions

View File

@ -29,6 +29,7 @@ import '../src/database/database.dart';
import '../matrix_api/utils/logs.dart';
import '../src/utils/run_in_background.dart';
import '../src/utils/run_in_root.dart';
import '../matrix_api/utils/try_get_map_extension.dart';
const MEGOLM_KEY = EventTypes.MegolmBackup;
@ -88,7 +89,8 @@ class KeyManager {
Map<String, dynamic> content,
{bool forwarded = false,
Map<String, String> senderClaimedKeys,
bool uploaded = false}) {
bool uploaded = false,
Map<String, Map<String, int>> allowedAtIndex}) {
senderClaimedKeys ??= <String, String>{};
if (!senderClaimedKeys.containsKey('ed25519')) {
final device = client.getUserDeviceKeysByCurve25519Key(senderKey);
@ -123,6 +125,7 @@ class KeyManager {
key: client.userID,
senderKey: senderKey,
senderClaimedKeys: senderClaimedKeys,
allowedAtIndex: allowedAtIndex,
);
final oldFirstIndex =
oldSession?.inboundGroupSession?.first_known_index() ?? 0;
@ -151,6 +154,7 @@ class KeyManager {
inboundGroupSession.pickle(client.userID),
json.encode(content),
json.encode({}),
json.encode(allowedAtIndex ?? {}),
senderKey,
json.encode(senderClaimedKeys),
)
@ -290,6 +294,8 @@ class KeyManager {
wipe = true;
}
}
final inboundSess = await loadInboundGroupSession(room.id,
sess.outboundGroupSession.session_id(), encryption.identityKey);
if (!wipe) {
// next check if the devices in the room changed
final devicesToReceive = <DeviceKeys>[];
@ -350,6 +356,25 @@ class KeyManager {
try {
devicesToReceive.removeWhere((k) => k.blocked);
if (devicesToReceive.isNotEmpty) {
// update allowedAtIndex
for (final device in devicesToReceive) {
inboundSess.allowedAtIndex[device.userId] ??= <String, int>{};
if (!inboundSess.allowedAtIndex[device.userId]
.containsKey(device.deviceId) ||
inboundSess.allowedAtIndex[device.userId][device.deviceId] >
sess.outboundGroupSession.message_index()) {
inboundSess.allowedAtIndex[device.userId][device.deviceId] =
sess.outboundGroupSession.message_index();
}
}
if (client.database != null) {
await client.database.updateInboundGroupSessionAllowedAtIndex(
json.encode(inboundSess.allowedAtIndex),
client.id,
room.id,
sess.outboundGroupSession.session_id());
}
// send out the key
await client.sendToDeviceEncrypted(
devicesToReceive, 'm.room_key', rawSession);
}
@ -404,6 +429,7 @@ class KeyManager {
}
final deviceKeys = await room.getUserDeviceKeys();
final deviceKeyIds = _getDeviceKeyIdMap(deviceKeys);
deviceKeys.removeWhere((k) => k.blocked);
final outboundGroupSession = olm.OutboundGroupSession();
try {
outboundGroupSession.create();
@ -418,8 +444,15 @@ class KeyManager {
'session_id': outboundGroupSession.session_id(),
'session_key': outboundGroupSession.session_key(),
};
final allowedAtIndex = <String, Map<String, int>>{};
for (final device in deviceKeys) {
allowedAtIndex[device.userId] ??= <String, int>{};
allowedAtIndex[device.userId][device.deviceId] =
outboundGroupSession.message_index();
}
setInboundGroupSession(
roomId, rawSession['session_id'], encryption.identityKey, rawSession);
roomId, rawSession['session_id'], encryption.identityKey, rawSession,
allowedAtIndex: allowedAtIndex);
final sess = OutboundGroupSession(
devices: deviceKeyIds,
creationTime: DateTime.now(),
@ -428,7 +461,6 @@ class KeyManager {
key: client.userID,
);
try {
deviceKeys.removeWhere((k) => k.blocked);
await client.sendToDeviceEncrypted(deviceKeys, 'm.room_key', rawSession);
await storeOutboundGroupSession(roomId, sess);
_outboundGroupSessions[roomId] = sess;
@ -736,8 +768,9 @@ class KeyManager {
final sessionId = event.content['body']['session_id'];
final senderKey = event.content['body']['sender_key'];
// okay, let's see if we have this session at all
if ((await loadInboundGroupSession(room.id, sessionId, senderKey)) ==
null) {
final session =
await loadInboundGroupSession(room.id, sessionId, senderKey);
if (session == null) {
Logs().i('[KeyManager] Unknown session, ignoring');
return; // we don't have this session anyways
}
@ -761,6 +794,18 @@ class KeyManager {
Logs().i('[KeyManager] All checks out, forwarding key...');
// alright, we can forward the key
await roomKeyRequest.forwardKey();
} else if (!device.blocked &&
session.allowedAtIndex
.tryGet<Map<String, dynamic>>(device.userId)
?.tryGet(device.deviceId) !=
null) {
// if we know the user may see the message, then we can just forward the key.
// we do not need to check if the device is verified, just if it is not blocked,
// as that is the logic we already initially try to send out the room keys.
final index = session.allowedAtIndex[device.userId][device.deviceId];
Logs().i(
'[KeyManager] Valid foreign request, forwarding key at index $index...');
await roomKeyRequest.forwardKey(index);
} else {
Logs()
.i('[KeyManager] Asking client, if the key should be forwarded');
@ -903,7 +948,7 @@ class RoomKeyRequest extends ToDeviceEvent {
DeviceKeys get requestingDevice => request.devices.first;
Future<void> forwardKey() async {
Future<void> forwardKey([int index]) async {
if (request.canceled) {
keyManager.incomingShareRequests.remove(request.requestId);
return; // request is canceled, don't send anything
@ -924,8 +969,8 @@ class RoomKeyRequest extends ToDeviceEvent {
(session.forwardingCurve25519KeyChain.isEmpty
? keyManager.encryption.fingerprintKey
: null);
message['session_key'] = session.inboundGroupSession
.export_session(session.inboundGroupSession.first_known_index());
message['session_key'] = session.inboundGroupSession.export_session(
index ?? session.inboundGroupSession.first_known_index());
// send the actual reply of the key back to the requester
await keyManager.client.sendToDeviceEncrypted(
[requestingDevice],

View File

@ -16,8 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import 'dart:convert';
import 'package:olm/olm.dart' as olm;
import '../../famedlysdk.dart';
@ -25,19 +23,42 @@ import '../../src/database/database.dart' show DbInboundGroupSession;
import '../../matrix_api/utils/logs.dart';
class SessionKey {
/// The raw json content of the key
Map<String, dynamic> content;
/// Map of stringified-index to event id, so that we can detect replay attacks
Map<String, String> indexes;
/// Map of userId to map of deviceId to index, that we know that device receivied, e.g. sending it ourself.
/// Used for automatically answering key requests
Map<String, Map<String, int>> allowedAtIndex;
/// Underlying olm [InboundGroupSession] object
olm.InboundGroupSession inboundGroupSession;
/// Key for libolm pickle / unpickle
final String key;
/// Forwarding keychain
List<String> get forwardingCurve25519KeyChain =>
(content['forwarding_curve25519_key_chain'] != null
? List<String>.from(content['forwarding_curve25519_key_chain'])
: null) ??
<String>[];
/// Claimed keys of the original sender
Map<String, String> senderClaimedKeys;
/// Sender curve25519 key
String senderKey;
/// Is this session valid?
bool get isValid => inboundGroupSession != null;
/// roomId for this session
String roomId;
/// Id of this session
String sessionId;
SessionKey(
@ -45,17 +66,22 @@ class SessionKey {
this.inboundGroupSession,
this.key,
this.indexes,
this.allowedAtIndex,
this.roomId,
this.sessionId,
String senderKey,
Map<String, String> senderClaimedKeys}) {
_setSenderKey(senderKey);
_setSenderClaimedKeys(senderClaimedKeys);
indexes ??= <String, String>{};
allowedAtIndex ??= <String, Map<String, int>>{};
}
SessionKey.fromDb(DbInboundGroupSession dbEntry, String key) : key = key {
final parsedContent = Event.getMapFromPayload(dbEntry.content);
final parsedIndexes = Event.getMapFromPayload(dbEntry.indexes);
final parsedAllowedAtIndex =
Event.getMapFromPayload(dbEntry.allowedAtIndex);
final parsedSenderClaimedKeys =
Event.getMapFromPayload(dbEntry.senderClaimedKeys);
content =
@ -68,6 +94,14 @@ class SessionKey {
} catch (e) {
indexes = <String, String>{};
}
try {
allowedAtIndex = parsedAllowedAtIndex != null
? Map<String, Map<String, int>>.from(parsedAllowedAtIndex
.map((k, v) => MapEntry(k, Map<String, int>.from(v))))
: <String, Map<String, int>>{};
} catch (e) {
allowedAtIndex = <String, Map<String, int>>{};
}
roomId = dbEntry.roomId;
sessionId = dbEntry.sessionId;
_setSenderKey(dbEntry.senderKey);
@ -98,23 +132,8 @@ class SessionKey {
: <String, String>{}));
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (content != null) {
data['content'] = content;
}
if (indexes != null) {
data['indexes'] = indexes;
}
data['inboundGroupSession'] = inboundGroupSession.pickle(key);
return data;
}
void dispose() {
inboundGroupSession?.free();
inboundGroupSession = null;
}
@override
String toString() => json.encode(toJson());
}

View File

@ -54,7 +54,7 @@ class Database extends _$Database {
Database.connect(DatabaseConnection connection) : super.connect(connection);
@override
int get schemaVersion => 7;
int get schemaVersion => 8;
int get maxFileSize => 1 * 1024 * 1024;
@ -136,6 +136,12 @@ class Database extends _$Database {
await delete(rooms).go();
await delete(outboundGroupSessions).go();
await customStatement('UPDATE clients SET prev_batch = null');
from++;
}
if (from == 7) {
await m.addColumnIfNotExists(
inboundGroupSessions, inboundGroupSessions.allowedAtIndex);
from++;
}
} catch (e, s) {
Logs().e('Database migration failed', e, s);

View File

@ -2262,6 +2262,7 @@ class DbInboundGroupSession extends DataClass
final String pickle;
final String content;
final String indexes;
final String allowedAtIndex;
final bool uploaded;
final String senderKey;
final String senderClaimedKeys;
@ -2272,6 +2273,7 @@ class DbInboundGroupSession extends DataClass
@required this.pickle,
this.content,
this.indexes,
this.allowedAtIndex,
this.uploaded,
this.senderKey,
this.senderClaimedKeys});
@ -2295,6 +2297,8 @@ class DbInboundGroupSession extends DataClass
stringType.mapFromDatabaseResponse(data['${effectivePrefix}content']),
indexes:
stringType.mapFromDatabaseResponse(data['${effectivePrefix}indexes']),
allowedAtIndex: stringType
.mapFromDatabaseResponse(data['${effectivePrefix}allowed_at_index']),
uploaded:
boolType.mapFromDatabaseResponse(data['${effectivePrefix}uploaded']),
senderKey: stringType
@ -2324,6 +2328,9 @@ class DbInboundGroupSession extends DataClass
if (!nullToAbsent || indexes != null) {
map['indexes'] = Variable<String>(indexes);
}
if (!nullToAbsent || allowedAtIndex != null) {
map['allowed_at_index'] = Variable<String>(allowedAtIndex);
}
if (!nullToAbsent || uploaded != null) {
map['uploaded'] = Variable<bool>(uploaded);
}
@ -2354,6 +2361,9 @@ class DbInboundGroupSession extends DataClass
indexes: indexes == null && nullToAbsent
? const Value.absent()
: Value(indexes),
allowedAtIndex: allowedAtIndex == null && nullToAbsent
? const Value.absent()
: Value(allowedAtIndex),
uploaded: uploaded == null && nullToAbsent
? const Value.absent()
: Value(uploaded),
@ -2376,6 +2386,7 @@ class DbInboundGroupSession extends DataClass
pickle: serializer.fromJson<String>(json['pickle']),
content: serializer.fromJson<String>(json['content']),
indexes: serializer.fromJson<String>(json['indexes']),
allowedAtIndex: serializer.fromJson<String>(json['allowed_at_index']),
uploaded: serializer.fromJson<bool>(json['uploaded']),
senderKey: serializer.fromJson<String>(json['sender_key']),
senderClaimedKeys:
@ -2392,6 +2403,7 @@ class DbInboundGroupSession extends DataClass
'pickle': serializer.toJson<String>(pickle),
'content': serializer.toJson<String>(content),
'indexes': serializer.toJson<String>(indexes),
'allowed_at_index': serializer.toJson<String>(allowedAtIndex),
'uploaded': serializer.toJson<bool>(uploaded),
'sender_key': serializer.toJson<String>(senderKey),
'sender_claimed_keys': serializer.toJson<String>(senderClaimedKeys),
@ -2405,6 +2417,7 @@ class DbInboundGroupSession extends DataClass
String pickle,
String content,
String indexes,
String allowedAtIndex,
bool uploaded,
String senderKey,
String senderClaimedKeys}) =>
@ -2415,6 +2428,7 @@ class DbInboundGroupSession extends DataClass
pickle: pickle ?? this.pickle,
content: content ?? this.content,
indexes: indexes ?? this.indexes,
allowedAtIndex: allowedAtIndex ?? this.allowedAtIndex,
uploaded: uploaded ?? this.uploaded,
senderKey: senderKey ?? this.senderKey,
senderClaimedKeys: senderClaimedKeys ?? this.senderClaimedKeys,
@ -2428,6 +2442,7 @@ class DbInboundGroupSession extends DataClass
..write('pickle: $pickle, ')
..write('content: $content, ')
..write('indexes: $indexes, ')
..write('allowedAtIndex: $allowedAtIndex, ')
..write('uploaded: $uploaded, ')
..write('senderKey: $senderKey, ')
..write('senderClaimedKeys: $senderClaimedKeys')
@ -2449,9 +2464,11 @@ class DbInboundGroupSession extends DataClass
$mrjc(
indexes.hashCode,
$mrjc(
uploaded.hashCode,
$mrjc(senderKey.hashCode,
senderClaimedKeys.hashCode)))))))));
allowedAtIndex.hashCode,
$mrjc(
uploaded.hashCode,
$mrjc(senderKey.hashCode,
senderClaimedKeys.hashCode))))))))));
@override
bool operator ==(dynamic other) =>
identical(this, other) ||
@ -2462,6 +2479,7 @@ class DbInboundGroupSession extends DataClass
other.pickle == this.pickle &&
other.content == this.content &&
other.indexes == this.indexes &&
other.allowedAtIndex == this.allowedAtIndex &&
other.uploaded == this.uploaded &&
other.senderKey == this.senderKey &&
other.senderClaimedKeys == this.senderClaimedKeys);
@ -2475,6 +2493,7 @@ class InboundGroupSessionsCompanion
final Value<String> pickle;
final Value<String> content;
final Value<String> indexes;
final Value<String> allowedAtIndex;
final Value<bool> uploaded;
final Value<String> senderKey;
final Value<String> senderClaimedKeys;
@ -2485,6 +2504,7 @@ class InboundGroupSessionsCompanion
this.pickle = const Value.absent(),
this.content = const Value.absent(),
this.indexes = const Value.absent(),
this.allowedAtIndex = const Value.absent(),
this.uploaded = const Value.absent(),
this.senderKey = const Value.absent(),
this.senderClaimedKeys = const Value.absent(),
@ -2496,6 +2516,7 @@ class InboundGroupSessionsCompanion
@required String pickle,
this.content = const Value.absent(),
this.indexes = const Value.absent(),
this.allowedAtIndex = const Value.absent(),
this.uploaded = const Value.absent(),
this.senderKey = const Value.absent(),
this.senderClaimedKeys = const Value.absent(),
@ -2510,6 +2531,7 @@ class InboundGroupSessionsCompanion
Expression<String> pickle,
Expression<String> content,
Expression<String> indexes,
Expression<String> allowedAtIndex,
Expression<bool> uploaded,
Expression<String> senderKey,
Expression<String> senderClaimedKeys,
@ -2521,6 +2543,7 @@ class InboundGroupSessionsCompanion
if (pickle != null) 'pickle': pickle,
if (content != null) 'content': content,
if (indexes != null) 'indexes': indexes,
if (allowedAtIndex != null) 'allowed_at_index': allowedAtIndex,
if (uploaded != null) 'uploaded': uploaded,
if (senderKey != null) 'sender_key': senderKey,
if (senderClaimedKeys != null) 'sender_claimed_keys': senderClaimedKeys,
@ -2534,6 +2557,7 @@ class InboundGroupSessionsCompanion
Value<String> pickle,
Value<String> content,
Value<String> indexes,
Value<String> allowedAtIndex,
Value<bool> uploaded,
Value<String> senderKey,
Value<String> senderClaimedKeys}) {
@ -2544,6 +2568,7 @@ class InboundGroupSessionsCompanion
pickle: pickle ?? this.pickle,
content: content ?? this.content,
indexes: indexes ?? this.indexes,
allowedAtIndex: allowedAtIndex ?? this.allowedAtIndex,
uploaded: uploaded ?? this.uploaded,
senderKey: senderKey ?? this.senderKey,
senderClaimedKeys: senderClaimedKeys ?? this.senderClaimedKeys,
@ -2571,6 +2596,9 @@ class InboundGroupSessionsCompanion
if (indexes.present) {
map['indexes'] = Variable<String>(indexes.value);
}
if (allowedAtIndex.present) {
map['allowed_at_index'] = Variable<String>(allowedAtIndex.value);
}
if (uploaded.present) {
map['uploaded'] = Variable<bool>(uploaded.value);
}
@ -2592,6 +2620,7 @@ class InboundGroupSessionsCompanion
..write('pickle: $pickle, ')
..write('content: $content, ')
..write('indexes: $indexes, ')
..write('allowedAtIndex: $allowedAtIndex, ')
..write('uploaded: $uploaded, ')
..write('senderKey: $senderKey, ')
..write('senderClaimedKeys: $senderClaimedKeys')
@ -2653,6 +2682,16 @@ class InboundGroupSessions extends Table
$customConstraints: '');
}
final VerificationMeta _allowedAtIndexMeta =
const VerificationMeta('allowedAtIndex');
GeneratedTextColumn _allowedAtIndex;
GeneratedTextColumn get allowedAtIndex =>
_allowedAtIndex ??= _constructAllowedAtIndex();
GeneratedTextColumn _constructAllowedAtIndex() {
return GeneratedTextColumn('allowed_at_index', $tableName, true,
$customConstraints: '');
}
final VerificationMeta _uploadedMeta = const VerificationMeta('uploaded');
GeneratedBoolColumn _uploaded;
GeneratedBoolColumn get uploaded => _uploaded ??= _constructUploaded();
@ -2688,6 +2727,7 @@ class InboundGroupSessions extends Table
pickle,
content,
indexes,
allowedAtIndex,
uploaded,
senderKey,
senderClaimedKeys
@ -2736,6 +2776,12 @@ class InboundGroupSessions extends Table
context.handle(_indexesMeta,
indexes.isAcceptableOrUnknown(data['indexes'], _indexesMeta));
}
if (data.containsKey('allowed_at_index')) {
context.handle(
_allowedAtIndexMeta,
allowedAtIndex.isAcceptableOrUnknown(
data['allowed_at_index'], _allowedAtIndexMeta));
}
if (data.containsKey('uploaded')) {
context.handle(_uploadedMeta,
uploaded.isAcceptableOrUnknown(data['uploaded'], _uploadedMeta));
@ -6293,10 +6339,11 @@ abstract class _$Database extends GeneratedDatabase {
String pickle,
String content,
String indexes,
String allowed_at_index,
String sender_key,
String sender_claimed_keys) {
return customInsert(
'INSERT OR REPLACE INTO inbound_group_sessions (client_id, room_id, session_id, pickle, content, indexes, sender_key, sender_claimed_keys) VALUES (:client_id, :room_id, :session_id, :pickle, :content, :indexes, :sender_key, :sender_claimed_keys)',
'INSERT OR REPLACE INTO inbound_group_sessions (client_id, room_id, session_id, pickle, content, indexes, allowed_at_index, sender_key, sender_claimed_keys) VALUES (:client_id, :room_id, :session_id, :pickle, :content, :indexes, :allowed_at_index, :sender_key, :sender_claimed_keys)',
variables: [
Variable.withInt(client_id),
Variable.withString(room_id),
@ -6304,6 +6351,7 @@ abstract class _$Database extends GeneratedDatabase {
Variable.withString(pickle),
Variable.withString(content),
Variable.withString(indexes),
Variable.withString(allowed_at_index),
Variable.withString(sender_key),
Variable.withString(sender_claimed_keys)
],
@ -6326,6 +6374,21 @@ abstract class _$Database extends GeneratedDatabase {
);
}
Future<int> updateInboundGroupSessionAllowedAtIndex(String allowed_at_index,
int client_id, String room_id, String session_id) {
return customUpdate(
'UPDATE inbound_group_sessions SET allowed_at_index = :allowed_at_index WHERE client_id = :client_id AND room_id = :room_id AND session_id = :session_id',
variables: [
Variable.withString(allowed_at_index),
Variable.withInt(client_id),
Variable.withString(room_id),
Variable.withString(session_id)
],
updates: {inboundGroupSessions},
updateKind: UpdateKind.update,
);
}
Selectable<DbInboundGroupSession> getInboundGroupSessionsToUpload() {
return customSelect(
'SELECT * FROM inbound_group_sessions WHERE uploaded = false LIMIT 500',

View File

@ -71,6 +71,7 @@ CREATE TABLE inbound_group_sessions (
pickle TEXT NOT NULL,
content TEXT,
indexes TEXT,
allowed_at_index TEXT,
uploaded BOOLEAN DEFAULT false,
sender_key TEXT,
sender_claimed_keys TEXT,
@ -189,8 +190,9 @@ removeOutboundGroupSession: DELETE FROM outbound_group_sessions WHERE client_id
dbGetInboundGroupSessionKey: SELECT * FROM inbound_group_sessions WHERE client_id = :client_id AND room_id = :room_id AND session_id = :session_id;
dbGetInboundGroupSessionKeys: SELECT * FROM inbound_group_sessions WHERE client_id = :client_id AND room_id = :room_id;
getAllInboundGroupSessions: SELECT * FROM inbound_group_sessions WHERE client_id = :client_id;
storeInboundGroupSession: INSERT OR REPLACE INTO inbound_group_sessions (client_id, room_id, session_id, pickle, content, indexes, sender_key, sender_claimed_keys) VALUES (:client_id, :room_id, :session_id, :pickle, :content, :indexes, :sender_key, :sender_claimed_keys);
storeInboundGroupSession: INSERT OR REPLACE INTO inbound_group_sessions (client_id, room_id, session_id, pickle, content, indexes, allowed_at_index, sender_key, sender_claimed_keys) VALUES (:client_id, :room_id, :session_id, :pickle, :content, :indexes, :allowed_at_index, :sender_key, :sender_claimed_keys);
updateInboundGroupSessionIndexes: UPDATE inbound_group_sessions SET indexes = :indexes WHERE client_id = :client_id AND room_id = :room_id AND session_id = :session_id;
updateInboundGroupSessionAllowedAtIndex: UPDATE inbound_group_sessions SET allowed_at_index = :allowed_at_index WHERE client_id = :client_id AND room_id = :room_id AND session_id = :session_id;
getInboundGroupSessionsToUpload: SELECT * FROM inbound_group_sessions WHERE uploaded = false LIMIT 500;
markInboundGroupSessionAsUploaded: UPDATE inbound_group_sessions SET uploaded = true WHERE client_id = :client_id AND room_id = :room_id AND session_id = :session_id;
markInboundGroupSessionsAsNeedingUpload: UPDATE inbound_group_sessions SET uploaded = false WHERE client_id = :client_id;

View File

@ -108,11 +108,11 @@ void main() {
expect(
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
true);
expect(
client.encryption.keyManager.getInboundGroupSession(roomId,
sess.outboundGroupSession.session_id(), client.identityKey) !=
null,
true);
var inbound = client.encryption.keyManager.getInboundGroupSession(
roomId, sess.outboundGroupSession.session_id(), client.identityKey);
expect(inbound != null, true);
expect(inbound.allowedAtIndex['@alice:example.com']['JLAFKJWSCS'], 0);
expect(inbound.allowedAtIndex['@alice:example.com']['OTHERDEVICE'], 0);
// rotate after too many messages
sess.sentMessages = 300;
@ -159,6 +159,8 @@ void main() {
// do not rotate if new device is added
sess =
await client.encryption.keyManager.createOutboundGroupSession(roomId);
sess.outboundGroupSession.encrypt(
'foxies'); // so that the new device will have a different index
client.userDeviceKeys['@alice:example.com'].deviceKeys['NEWDEVICE'] =
DeviceKeys.fromJson({
'user_id': '@alice:example.com',
@ -177,6 +179,11 @@ void main() {
expect(
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
true);
inbound = client.encryption.keyManager.getInboundGroupSession(
roomId, sess.outboundGroupSession.session_id(), client.identityKey);
expect(inbound.allowedAtIndex['@alice:example.com']['JLAFKJWSCS'], 0);
expect(inbound.allowedAtIndex['@alice:example.com']['OTHERDEVICE'], 0);
expect(inbound.allowedAtIndex['@alice:example.com']['NEWDEVICE'], 1);
// do not rotate if new user is added
member.content['membership'] = 'leave';

View File

@ -93,6 +93,9 @@ void main() {
await matrix
.userDeviceKeys['@alice:example.com'].deviceKeys['OTHERDEVICE']
.setVerified(true);
final session = await matrix.encryption.keyManager
.loadInboundGroupSession(
'!726s6s6q:example.com', validSessionId, validSenderKey);
// test a successful share
var event = ToDeviceEvent(
sender: '@alice:example.com',
@ -115,8 +118,58 @@ void main() {
(k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')),
true);
// test a successful foreign share
FakeMatrixApi.calledEndpoints.clear();
session.allowedAtIndex['@test:fakeServer.notExisting'] = <String, int>{
'OTHERDEVICE': 0,
};
event = ToDeviceEvent(
sender: '@test:fakeServer.notExisting',
type: 'm.room_key_request',
content: {
'action': 'request',
'body': {
'algorithm': AlgorithmTypes.megolmV1AesSha2,
'room_id': '!726s6s6q:example.com',
'sender_key': validSenderKey,
'session_id': validSessionId,
},
'request_id': 'request_a1',
'requesting_device_id': 'OTHERDEVICE',
});
await matrix.encryption.keyManager.handleToDeviceEvent(event);
Logs().i(FakeMatrixApi.calledEndpoints.keys.toString());
expect(
FakeMatrixApi.calledEndpoints.keys.any(
(k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')),
true);
session.allowedAtIndex.remove('@test:fakeServer.notExisting');
// test various fail scenarios
// unknown person
FakeMatrixApi.calledEndpoints.clear();
event = ToDeviceEvent(
sender: '@test:fakeServer.notExisting',
type: 'm.room_key_request',
content: {
'action': 'request',
'body': {
'algorithm': AlgorithmTypes.megolmV1AesSha2,
'room_id': '!726s6s6q:example.com',
'sender_key': validSenderKey,
'session_id': validSessionId,
},
'request_id': 'request_a2',
'requesting_device_id': 'OTHERDEVICE',
});
await matrix.encryption.keyManager.handleToDeviceEvent(event);
Logs().i(FakeMatrixApi.calledEndpoints.keys.toString());
expect(
FakeMatrixApi.calledEndpoints.keys.any(
(k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')),
false);
// no body
FakeMatrixApi.calledEndpoints.clear();
event = ToDeviceEvent(