feat: Auto-Share megolm sessions with other users we know for a fact are allowed to see said message
This commit is contained in:
parent
7188d23eb3
commit
df1c249011
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -106,11 +106,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;
|
||||
|
|
@ -157,6 +157,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',
|
||||
|
|
@ -175,6 +177,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';
|
||||
|
|
|
|||
|
|
@ -91,6 +91,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',
|
||||
|
|
@ -113,8 +116,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(
|
||||
|
|
|
|||
Loading…
Reference in New Issue