Add rotation of outbound group sessions

This commit is contained in:
Sorunome 2020-05-18 18:33:16 +02:00
parent 3b1c81b4c7
commit a0fe8f4bad
No known key found for this signature in database
GPG key ID: B19471D07FC9BE9C
5 changed files with 151 additions and 17 deletions

View file

@ -1347,7 +1347,8 @@ class Client {
// man-in-the-middle attacks! // man-in-the-middle attacks!
final room = getRoomById(roomID); final room = getRoomById(roomID);
if (room == null || if (room == null ||
(event['type'] == 'm.room.encryption' && room.encrypted)) { (event['type'] == 'm.room.encryption' && room.encrypted &&
event['content']['algorithm'] != room.getState('m.room.encryption')?.content['algorithm'])) {
return; return;
} }

View file

@ -13,7 +13,7 @@ class Database extends _$Database {
Database(QueryExecutor e) : super(e); Database(QueryExecutor e) : super(e);
@override @override
int get schemaVersion => 2; int get schemaVersion => 3;
int get maxFileSize => 1 * 1024 * 1024; int get maxFileSize => 1 * 1024 * 1024;
@ -36,6 +36,11 @@ class Database extends _$Database {
await m.createIndex(accountDataIndex); await m.createIndex(accountDataIndex);
await m.createIndex(roomAccountDataIndex); await m.createIndex(roomAccountDataIndex);
await m.createIndex(presencesIndex); await m.createIndex(presencesIndex);
from++;
}
if (from == 2) {
await m.deleteTable('outbound_group_sessions');
await m.createTable(outboundGroupSessions);
} }
}, },
); );

View file

@ -1313,17 +1313,22 @@ class DbOutboundGroupSession extends DataClass
final String roomId; final String roomId;
final String pickle; final String pickle;
final String deviceIds; final String deviceIds;
final DateTime creationTime;
final int sentMessages;
DbOutboundGroupSession( DbOutboundGroupSession(
{@required this.clientId, {@required this.clientId,
@required this.roomId, @required this.roomId,
@required this.pickle, @required this.pickle,
@required this.deviceIds}); @required this.deviceIds,
@required this.creationTime,
@required this.sentMessages});
factory DbOutboundGroupSession.fromData( factory DbOutboundGroupSession.fromData(
Map<String, dynamic> data, GeneratedDatabase db, Map<String, dynamic> data, GeneratedDatabase db,
{String prefix}) { {String prefix}) {
final effectivePrefix = prefix ?? ''; final effectivePrefix = prefix ?? '';
final intType = db.typeSystem.forDartType<int>(); final intType = db.typeSystem.forDartType<int>();
final stringType = db.typeSystem.forDartType<String>(); final stringType = db.typeSystem.forDartType<String>();
final dateTimeType = db.typeSystem.forDartType<DateTime>();
return DbOutboundGroupSession( return DbOutboundGroupSession(
clientId: clientId:
intType.mapFromDatabaseResponse(data['${effectivePrefix}client_id']), intType.mapFromDatabaseResponse(data['${effectivePrefix}client_id']),
@ -1333,6 +1338,10 @@ class DbOutboundGroupSession extends DataClass
stringType.mapFromDatabaseResponse(data['${effectivePrefix}pickle']), stringType.mapFromDatabaseResponse(data['${effectivePrefix}pickle']),
deviceIds: stringType deviceIds: stringType
.mapFromDatabaseResponse(data['${effectivePrefix}device_ids']), .mapFromDatabaseResponse(data['${effectivePrefix}device_ids']),
creationTime: dateTimeType
.mapFromDatabaseResponse(data['${effectivePrefix}creation_time']),
sentMessages: intType
.mapFromDatabaseResponse(data['${effectivePrefix}sent_messages']),
); );
} }
@override @override
@ -1350,6 +1359,12 @@ class DbOutboundGroupSession extends DataClass
if (!nullToAbsent || deviceIds != null) { if (!nullToAbsent || deviceIds != null) {
map['device_ids'] = Variable<String>(deviceIds); map['device_ids'] = Variable<String>(deviceIds);
} }
if (!nullToAbsent || creationTime != null) {
map['creation_time'] = Variable<DateTime>(creationTime);
}
if (!nullToAbsent || sentMessages != null) {
map['sent_messages'] = Variable<int>(sentMessages);
}
return map; return map;
} }
@ -1361,6 +1376,8 @@ class DbOutboundGroupSession extends DataClass
roomId: serializer.fromJson<String>(json['room_id']), roomId: serializer.fromJson<String>(json['room_id']),
pickle: serializer.fromJson<String>(json['pickle']), pickle: serializer.fromJson<String>(json['pickle']),
deviceIds: serializer.fromJson<String>(json['device_ids']), deviceIds: serializer.fromJson<String>(json['device_ids']),
creationTime: serializer.fromJson<DateTime>(json['creation_time']),
sentMessages: serializer.fromJson<int>(json['sent_messages']),
); );
} }
@override @override
@ -1371,16 +1388,25 @@ class DbOutboundGroupSession extends DataClass
'room_id': serializer.toJson<String>(roomId), 'room_id': serializer.toJson<String>(roomId),
'pickle': serializer.toJson<String>(pickle), 'pickle': serializer.toJson<String>(pickle),
'device_ids': serializer.toJson<String>(deviceIds), 'device_ids': serializer.toJson<String>(deviceIds),
'creation_time': serializer.toJson<DateTime>(creationTime),
'sent_messages': serializer.toJson<int>(sentMessages),
}; };
} }
DbOutboundGroupSession copyWith( DbOutboundGroupSession copyWith(
{int clientId, String roomId, String pickle, String deviceIds}) => {int clientId,
String roomId,
String pickle,
String deviceIds,
DateTime creationTime,
int sentMessages}) =>
DbOutboundGroupSession( DbOutboundGroupSession(
clientId: clientId ?? this.clientId, clientId: clientId ?? this.clientId,
roomId: roomId ?? this.roomId, roomId: roomId ?? this.roomId,
pickle: pickle ?? this.pickle, pickle: pickle ?? this.pickle,
deviceIds: deviceIds ?? this.deviceIds, deviceIds: deviceIds ?? this.deviceIds,
creationTime: creationTime ?? this.creationTime,
sentMessages: sentMessages ?? this.sentMessages,
); );
@override @override
String toString() { String toString() {
@ -1388,14 +1414,22 @@ class DbOutboundGroupSession extends DataClass
..write('clientId: $clientId, ') ..write('clientId: $clientId, ')
..write('roomId: $roomId, ') ..write('roomId: $roomId, ')
..write('pickle: $pickle, ') ..write('pickle: $pickle, ')
..write('deviceIds: $deviceIds') ..write('deviceIds: $deviceIds, ')
..write('creationTime: $creationTime, ')
..write('sentMessages: $sentMessages')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@override @override
int get hashCode => $mrjf($mrjc(clientId.hashCode, int get hashCode => $mrjf($mrjc(
$mrjc(roomId.hashCode, $mrjc(pickle.hashCode, deviceIds.hashCode)))); clientId.hashCode,
$mrjc(
roomId.hashCode,
$mrjc(
pickle.hashCode,
$mrjc(deviceIds.hashCode,
$mrjc(creationTime.hashCode, sentMessages.hashCode))))));
@override @override
bool operator ==(dynamic other) => bool operator ==(dynamic other) =>
identical(this, other) || identical(this, other) ||
@ -1403,7 +1437,9 @@ class DbOutboundGroupSession extends DataClass
other.clientId == this.clientId && other.clientId == this.clientId &&
other.roomId == this.roomId && other.roomId == this.roomId &&
other.pickle == this.pickle && other.pickle == this.pickle &&
other.deviceIds == this.deviceIds); other.deviceIds == this.deviceIds &&
other.creationTime == this.creationTime &&
other.sentMessages == this.sentMessages);
} }
class OutboundGroupSessionsCompanion class OutboundGroupSessionsCompanion
@ -1412,32 +1448,43 @@ class OutboundGroupSessionsCompanion
final Value<String> roomId; final Value<String> roomId;
final Value<String> pickle; final Value<String> pickle;
final Value<String> deviceIds; final Value<String> deviceIds;
final Value<DateTime> creationTime;
final Value<int> sentMessages;
const OutboundGroupSessionsCompanion({ const OutboundGroupSessionsCompanion({
this.clientId = const Value.absent(), this.clientId = const Value.absent(),
this.roomId = const Value.absent(), this.roomId = const Value.absent(),
this.pickle = const Value.absent(), this.pickle = const Value.absent(),
this.deviceIds = const Value.absent(), this.deviceIds = const Value.absent(),
this.creationTime = const Value.absent(),
this.sentMessages = const Value.absent(),
}); });
OutboundGroupSessionsCompanion.insert({ OutboundGroupSessionsCompanion.insert({
@required int clientId, @required int clientId,
@required String roomId, @required String roomId,
@required String pickle, @required String pickle,
@required String deviceIds, @required String deviceIds,
@required DateTime creationTime,
this.sentMessages = const Value.absent(),
}) : clientId = Value(clientId), }) : clientId = Value(clientId),
roomId = Value(roomId), roomId = Value(roomId),
pickle = Value(pickle), pickle = Value(pickle),
deviceIds = Value(deviceIds); deviceIds = Value(deviceIds),
creationTime = Value(creationTime);
static Insertable<DbOutboundGroupSession> custom({ static Insertable<DbOutboundGroupSession> custom({
Expression<int> clientId, Expression<int> clientId,
Expression<String> roomId, Expression<String> roomId,
Expression<String> pickle, Expression<String> pickle,
Expression<String> deviceIds, Expression<String> deviceIds,
Expression<DateTime> creationTime,
Expression<int> sentMessages,
}) { }) {
return RawValuesInsertable({ return RawValuesInsertable({
if (clientId != null) 'client_id': clientId, if (clientId != null) 'client_id': clientId,
if (roomId != null) 'room_id': roomId, if (roomId != null) 'room_id': roomId,
if (pickle != null) 'pickle': pickle, if (pickle != null) 'pickle': pickle,
if (deviceIds != null) 'device_ids': deviceIds, if (deviceIds != null) 'device_ids': deviceIds,
if (creationTime != null) 'creation_time': creationTime,
if (sentMessages != null) 'sent_messages': sentMessages,
}); });
} }
@ -1445,12 +1492,16 @@ class OutboundGroupSessionsCompanion
{Value<int> clientId, {Value<int> clientId,
Value<String> roomId, Value<String> roomId,
Value<String> pickle, Value<String> pickle,
Value<String> deviceIds}) { Value<String> deviceIds,
Value<DateTime> creationTime,
Value<int> sentMessages}) {
return OutboundGroupSessionsCompanion( return OutboundGroupSessionsCompanion(
clientId: clientId ?? this.clientId, clientId: clientId ?? this.clientId,
roomId: roomId ?? this.roomId, roomId: roomId ?? this.roomId,
pickle: pickle ?? this.pickle, pickle: pickle ?? this.pickle,
deviceIds: deviceIds ?? this.deviceIds, deviceIds: deviceIds ?? this.deviceIds,
creationTime: creationTime ?? this.creationTime,
sentMessages: sentMessages ?? this.sentMessages,
); );
} }
@ -1469,6 +1520,12 @@ class OutboundGroupSessionsCompanion
if (deviceIds.present) { if (deviceIds.present) {
map['device_ids'] = Variable<String>(deviceIds.value); map['device_ids'] = Variable<String>(deviceIds.value);
} }
if (creationTime.present) {
map['creation_time'] = Variable<DateTime>(creationTime.value);
}
if (sentMessages.present) {
map['sent_messages'] = Variable<int>(sentMessages.value);
}
return map; return map;
} }
} }
@ -1510,8 +1567,30 @@ class OutboundGroupSessions extends Table
$customConstraints: 'NOT NULL'); $customConstraints: 'NOT NULL');
} }
final VerificationMeta _creationTimeMeta =
const VerificationMeta('creationTime');
GeneratedDateTimeColumn _creationTime;
GeneratedDateTimeColumn get creationTime =>
_creationTime ??= _constructCreationTime();
GeneratedDateTimeColumn _constructCreationTime() {
return GeneratedDateTimeColumn('creation_time', $tableName, false,
$customConstraints: 'NOT NULL');
}
final VerificationMeta _sentMessagesMeta =
const VerificationMeta('sentMessages');
GeneratedIntColumn _sentMessages;
GeneratedIntColumn get sentMessages =>
_sentMessages ??= _constructSentMessages();
GeneratedIntColumn _constructSentMessages() {
return GeneratedIntColumn('sent_messages', $tableName, false,
$customConstraints: 'NOT NULL DEFAULT \'0\'',
defaultValue: const CustomExpression<int>('\'0\''));
}
@override @override
List<GeneratedColumn> get $columns => [clientId, roomId, pickle, deviceIds]; List<GeneratedColumn> get $columns =>
[clientId, roomId, pickle, deviceIds, creationTime, sentMessages];
@override @override
OutboundGroupSessions get asDslTable => this; OutboundGroupSessions get asDslTable => this;
@override @override
@ -1548,6 +1627,20 @@ class OutboundGroupSessions extends Table
} else if (isInserting) { } else if (isInserting) {
context.missing(_deviceIdsMeta); context.missing(_deviceIdsMeta);
} }
if (data.containsKey('creation_time')) {
context.handle(
_creationTimeMeta,
creationTime.isAcceptableOrUnknown(
data['creation_time'], _creationTimeMeta));
} else if (isInserting) {
context.missing(_creationTimeMeta);
}
if (data.containsKey('sent_messages')) {
context.handle(
_sentMessagesMeta,
sentMessages.isAcceptableOrUnknown(
data['sent_messages'], _sentMessagesMeta));
}
return context; return context;
} }
@ -4778,6 +4871,8 @@ abstract class _$Database extends GeneratedDatabase {
roomId: row.readString('room_id'), roomId: row.readString('room_id'),
pickle: row.readString('pickle'), pickle: row.readString('pickle'),
deviceIds: row.readString('device_ids'), deviceIds: row.readString('device_ids'),
creationTime: row.readDateTime('creation_time'),
sentMessages: row.readInt('sent_messages'),
); );
} }
@ -4798,14 +4893,21 @@ abstract class _$Database extends GeneratedDatabase {
} }
Future<int> storeOutboundGroupSession( Future<int> storeOutboundGroupSession(
int client_id, String room_id, String pickle, String device_ids) { int client_id,
String room_id,
String pickle,
String device_ids,
DateTime creation_time,
int sent_messages) {
return customInsert( return customInsert(
'INSERT OR REPLACE INTO outbound_group_sessions (client_id, room_id, pickle, device_ids) VALUES (:client_id, :room_id, :pickle, :device_ids)', 'INSERT OR REPLACE INTO outbound_group_sessions (client_id, room_id, pickle, device_ids, creation_time, sent_messages) VALUES (:client_id, :room_id, :pickle, :device_ids, :creation_time, :sent_messages)',
variables: [ variables: [
Variable.withInt(client_id), Variable.withInt(client_id),
Variable.withString(room_id), Variable.withString(room_id),
Variable.withString(pickle), Variable.withString(pickle),
Variable.withString(device_ids) Variable.withString(device_ids),
Variable.withDateTime(creation_time),
Variable.withInt(sent_messages)
], ],
updates: {outboundGroupSessions}, updates: {outboundGroupSessions},
); );

View file

@ -46,6 +46,8 @@ CREATE TABLE outbound_group_sessions (
room_id TEXT NOT NULL, room_id TEXT NOT NULL,
pickle TEXT NOT NULL, pickle TEXT NOT NULL,
device_ids TEXT NOT NULL, device_ids TEXT NOT NULL,
creation_time DATETIME NOT NULL,
sent_messages INTEGER NOT NULL DEFAULT '0',
UNIQUE(client_id, room_id) UNIQUE(client_id, room_id)
) AS DbOutboundGroupSession; ) AS DbOutboundGroupSession;
CREATE INDEX outbound_group_sessions_index ON outbound_group_sessions(client_id); CREATE INDEX outbound_group_sessions_index ON outbound_group_sessions(client_id);
@ -156,7 +158,7 @@ getAllOlmSessions: SELECT * FROM olm_sessions WHERE client_id = :client_id;
storeOlmSession: INSERT OR REPLACE INTO olm_sessions (client_id, identity_key, session_id, pickle) VALUES (:client_id, :identitiy_key, :session_id, :pickle); storeOlmSession: INSERT OR REPLACE INTO olm_sessions (client_id, identity_key, session_id, pickle) VALUES (:client_id, :identitiy_key, :session_id, :pickle);
getAllOutboundGroupSessions: SELECT * FROM outbound_group_sessions WHERE client_id = :client_id; getAllOutboundGroupSessions: SELECT * FROM outbound_group_sessions WHERE client_id = :client_id;
dbGetOutboundGroupSession: SELECT * FROM outbound_group_sessions WHERE client_id = :client_id AND room_id = :room_id; dbGetOutboundGroupSession: SELECT * FROM outbound_group_sessions WHERE client_id = :client_id AND room_id = :room_id;
storeOutboundGroupSession: INSERT OR REPLACE INTO outbound_group_sessions (client_id, room_id, pickle, device_ids) VALUES (:client_id, :room_id, :pickle, :device_ids); storeOutboundGroupSession: INSERT OR REPLACE INTO outbound_group_sessions (client_id, room_id, pickle, device_ids, creation_time, sent_messages) VALUES (:client_id, :room_id, :pickle, :device_ids, :creation_time, :sent_messages);
removeOutboundGroupSession: DELETE FROM outbound_group_sessions WHERE client_id = :client_id AND room_id = :room_id; removeOutboundGroupSession: DELETE FROM outbound_group_sessions WHERE client_id = :client_id AND room_id = :room_id;
dbGetInboundGroupSessionKey: SELECT * FROM inbound_group_sessions WHERE client_id = :client_id AND room_id = :room_id AND session_id = :session_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; dbGetInboundGroupSessionKeys: SELECT * FROM inbound_group_sessions WHERE client_id = :client_id AND room_id = :room_id;

View file

@ -90,6 +90,8 @@ class Room {
olm.OutboundGroupSession _outboundGroupSession; olm.OutboundGroupSession _outboundGroupSession;
List<String> _outboundGroupSessionDevices; List<String> _outboundGroupSessionDevices;
DateTime _outboundGroupSessionCreationTime;
int _outboundGroupSessionSentMessages;
double _newestSortOrder; double _newestSortOrder;
double _oldestSortOrder; double _oldestSortOrder;
@ -147,6 +149,8 @@ class Room {
await client.sendToDevice(deviceKeys, 'm.room_key', rawSession); await client.sendToDevice(deviceKeys, 'm.room_key', rawSession);
_outboundGroupSession = outboundGroupSession; _outboundGroupSession = outboundGroupSession;
_outboundGroupSessionDevices = outboundGroupSessionDevices; _outboundGroupSessionDevices = outboundGroupSessionDevices;
_outboundGroupSessionCreationTime = DateTime.now();
_outboundGroupSessionSentMessages = 0;
await _storeOutboundGroupSession(); await _storeOutboundGroupSession();
} catch (e) { } catch (e) {
print( print(
@ -161,7 +165,8 @@ class Room {
if (_outboundGroupSession == null) return; if (_outboundGroupSession == null) return;
await client.database?.storeOutboundGroupSession( await client.database?.storeOutboundGroupSession(
client.id, id, _outboundGroupSession.pickle(client.userID), client.id, id, _outboundGroupSession.pickle(client.userID),
json.encode(_outboundGroupSessionDevices), json.encode(_outboundGroupSessionDevices), _outboundGroupSessionCreationTime,
_outboundGroupSessionSentMessages
); );
return; return;
} }
@ -171,14 +176,30 @@ class Room {
/// it wasn't necessary. /// it wasn't necessary.
Future<bool> clearOutboundGroupSession({bool wipe = false}) async { Future<bool> clearOutboundGroupSession({bool wipe = false}) async {
if (!wipe && _outboundGroupSessionDevices != null) { if (!wipe && _outboundGroupSessionDevices != null) {
// first check if the devices in the room changed
var deviceKeys = await getUserDeviceKeys(); var deviceKeys = await getUserDeviceKeys();
var outboundGroupSessionDevices = <String>[]; var outboundGroupSessionDevices = <String>[];
for (var keys in deviceKeys) { for (var keys in deviceKeys) {
if (!keys.blocked) outboundGroupSessionDevices.add(keys.deviceId); if (!keys.blocked) outboundGroupSessionDevices.add(keys.deviceId);
} }
outboundGroupSessionDevices.sort(); outboundGroupSessionDevices.sort();
if (outboundGroupSessionDevices.toString() == if (outboundGroupSessionDevices.toString() !=
_outboundGroupSessionDevices.toString()) { _outboundGroupSessionDevices.toString()) {
wipe = true;
}
// next check if it needs to be rotated
final encryptionContent = getState('m.room.encryption')?.content;
final maxMessages = encryptionContent != null && encryptionContent['rotation_period_msgs'] is int
? encryptionContent['rotation_period_msgs']
: 100;
final maxAge = encryptionContent != null && encryptionContent['rotation_period_ms'] is int
? encryptionContent['rotation_period_ms']
: 604800000; // default of one week
if (_outboundGroupSessionSentMessages >= maxMessages ||
_outboundGroupSessionCreationTime.add(Duration(milliseconds: maxAge)).isBefore(DateTime.now())) {
wipe = true;
}
if (!wipe) {
return false; return false;
} }
} }
@ -1705,6 +1726,8 @@ class Room {
client.userID, outboundSession.pickle); client.userID, outboundSession.pickle);
_outboundGroupSessionDevices = _outboundGroupSessionDevices =
List<String>.from(json.decode(outboundSession.deviceIds)); List<String>.from(json.decode(outboundSession.deviceIds));
_outboundGroupSessionCreationTime = outboundSession.creationTime;
_outboundGroupSessionSentMessages = outboundSession.sentMessages;
} catch (e) { } catch (e) {
_outboundGroupSession = null; _outboundGroupSession = null;
_outboundGroupSessionDevices = null; _outboundGroupSessionDevices = null;
@ -1748,6 +1771,7 @@ class Room {
'session_id': _outboundGroupSession.session_id(), 'session_id': _outboundGroupSession.session_id(),
if (mRelatesTo != null) 'm.relates_to': mRelatesTo, if (mRelatesTo != null) 'm.relates_to': mRelatesTo,
}; };
_outboundGroupSessionSentMessages++;
await _storeOutboundGroupSession(); await _storeOutboundGroupSession();
return encryptedPayload; return encryptedPayload;
} }