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!
final room = getRoomById(roomID);
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;
}

View file

@ -13,7 +13,7 @@ class Database extends _$Database {
Database(QueryExecutor e) : super(e);
@override
int get schemaVersion => 2;
int get schemaVersion => 3;
int get maxFileSize => 1 * 1024 * 1024;
@ -36,6 +36,11 @@ class Database extends _$Database {
await m.createIndex(accountDataIndex);
await m.createIndex(roomAccountDataIndex);
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 pickle;
final String deviceIds;
final DateTime creationTime;
final int sentMessages;
DbOutboundGroupSession(
{@required this.clientId,
@required this.roomId,
@required this.pickle,
@required this.deviceIds});
@required this.deviceIds,
@required this.creationTime,
@required this.sentMessages});
factory DbOutboundGroupSession.fromData(
Map<String, dynamic> data, GeneratedDatabase db,
{String prefix}) {
final effectivePrefix = prefix ?? '';
final intType = db.typeSystem.forDartType<int>();
final stringType = db.typeSystem.forDartType<String>();
final dateTimeType = db.typeSystem.forDartType<DateTime>();
return DbOutboundGroupSession(
clientId:
intType.mapFromDatabaseResponse(data['${effectivePrefix}client_id']),
@ -1333,6 +1338,10 @@ class DbOutboundGroupSession extends DataClass
stringType.mapFromDatabaseResponse(data['${effectivePrefix}pickle']),
deviceIds: stringType
.mapFromDatabaseResponse(data['${effectivePrefix}device_ids']),
creationTime: dateTimeType
.mapFromDatabaseResponse(data['${effectivePrefix}creation_time']),
sentMessages: intType
.mapFromDatabaseResponse(data['${effectivePrefix}sent_messages']),
);
}
@override
@ -1350,6 +1359,12 @@ class DbOutboundGroupSession extends DataClass
if (!nullToAbsent || deviceIds != null) {
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;
}
@ -1361,6 +1376,8 @@ class DbOutboundGroupSession extends DataClass
roomId: serializer.fromJson<String>(json['room_id']),
pickle: serializer.fromJson<String>(json['pickle']),
deviceIds: serializer.fromJson<String>(json['device_ids']),
creationTime: serializer.fromJson<DateTime>(json['creation_time']),
sentMessages: serializer.fromJson<int>(json['sent_messages']),
);
}
@override
@ -1371,16 +1388,25 @@ class DbOutboundGroupSession extends DataClass
'room_id': serializer.toJson<String>(roomId),
'pickle': serializer.toJson<String>(pickle),
'device_ids': serializer.toJson<String>(deviceIds),
'creation_time': serializer.toJson<DateTime>(creationTime),
'sent_messages': serializer.toJson<int>(sentMessages),
};
}
DbOutboundGroupSession copyWith(
{int clientId, String roomId, String pickle, String deviceIds}) =>
{int clientId,
String roomId,
String pickle,
String deviceIds,
DateTime creationTime,
int sentMessages}) =>
DbOutboundGroupSession(
clientId: clientId ?? this.clientId,
roomId: roomId ?? this.roomId,
pickle: pickle ?? this.pickle,
deviceIds: deviceIds ?? this.deviceIds,
creationTime: creationTime ?? this.creationTime,
sentMessages: sentMessages ?? this.sentMessages,
);
@override
String toString() {
@ -1388,14 +1414,22 @@ class DbOutboundGroupSession extends DataClass
..write('clientId: $clientId, ')
..write('roomId: $roomId, ')
..write('pickle: $pickle, ')
..write('deviceIds: $deviceIds')
..write('deviceIds: $deviceIds, ')
..write('creationTime: $creationTime, ')
..write('sentMessages: $sentMessages')
..write(')'))
.toString();
}
@override
int get hashCode => $mrjf($mrjc(clientId.hashCode,
$mrjc(roomId.hashCode, $mrjc(pickle.hashCode, deviceIds.hashCode))));
int get hashCode => $mrjf($mrjc(
clientId.hashCode,
$mrjc(
roomId.hashCode,
$mrjc(
pickle.hashCode,
$mrjc(deviceIds.hashCode,
$mrjc(creationTime.hashCode, sentMessages.hashCode))))));
@override
bool operator ==(dynamic other) =>
identical(this, other) ||
@ -1403,7 +1437,9 @@ class DbOutboundGroupSession extends DataClass
other.clientId == this.clientId &&
other.roomId == this.roomId &&
other.pickle == this.pickle &&
other.deviceIds == this.deviceIds);
other.deviceIds == this.deviceIds &&
other.creationTime == this.creationTime &&
other.sentMessages == this.sentMessages);
}
class OutboundGroupSessionsCompanion
@ -1412,32 +1448,43 @@ class OutboundGroupSessionsCompanion
final Value<String> roomId;
final Value<String> pickle;
final Value<String> deviceIds;
final Value<DateTime> creationTime;
final Value<int> sentMessages;
const OutboundGroupSessionsCompanion({
this.clientId = const Value.absent(),
this.roomId = const Value.absent(),
this.pickle = const Value.absent(),
this.deviceIds = const Value.absent(),
this.creationTime = const Value.absent(),
this.sentMessages = const Value.absent(),
});
OutboundGroupSessionsCompanion.insert({
@required int clientId,
@required String roomId,
@required String pickle,
@required String deviceIds,
@required DateTime creationTime,
this.sentMessages = const Value.absent(),
}) : clientId = Value(clientId),
roomId = Value(roomId),
pickle = Value(pickle),
deviceIds = Value(deviceIds);
deviceIds = Value(deviceIds),
creationTime = Value(creationTime);
static Insertable<DbOutboundGroupSession> custom({
Expression<int> clientId,
Expression<String> roomId,
Expression<String> pickle,
Expression<String> deviceIds,
Expression<DateTime> creationTime,
Expression<int> sentMessages,
}) {
return RawValuesInsertable({
if (clientId != null) 'client_id': clientId,
if (roomId != null) 'room_id': roomId,
if (pickle != null) 'pickle': pickle,
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<String> roomId,
Value<String> pickle,
Value<String> deviceIds}) {
Value<String> deviceIds,
Value<DateTime> creationTime,
Value<int> sentMessages}) {
return OutboundGroupSessionsCompanion(
clientId: clientId ?? this.clientId,
roomId: roomId ?? this.roomId,
pickle: pickle ?? this.pickle,
deviceIds: deviceIds ?? this.deviceIds,
creationTime: creationTime ?? this.creationTime,
sentMessages: sentMessages ?? this.sentMessages,
);
}
@ -1469,6 +1520,12 @@ class OutboundGroupSessionsCompanion
if (deviceIds.present) {
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;
}
}
@ -1510,8 +1567,30 @@ class OutboundGroupSessions extends Table
$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
List<GeneratedColumn> get $columns => [clientId, roomId, pickle, deviceIds];
List<GeneratedColumn> get $columns =>
[clientId, roomId, pickle, deviceIds, creationTime, sentMessages];
@override
OutboundGroupSessions get asDslTable => this;
@override
@ -1548,6 +1627,20 @@ class OutboundGroupSessions extends Table
} else if (isInserting) {
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;
}
@ -4778,6 +4871,8 @@ abstract class _$Database extends GeneratedDatabase {
roomId: row.readString('room_id'),
pickle: row.readString('pickle'),
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(
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(
'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: [
Variable.withInt(client_id),
Variable.withString(room_id),
Variable.withString(pickle),
Variable.withString(device_ids)
Variable.withString(device_ids),
Variable.withDateTime(creation_time),
Variable.withInt(sent_messages)
],
updates: {outboundGroupSessions},
);

View file

@ -46,6 +46,8 @@ CREATE TABLE outbound_group_sessions (
room_id TEXT NOT NULL,
pickle 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)
) AS DbOutboundGroupSession;
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);
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;
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;
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;

View file

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