lazy-load group session keys
This commit is contained in:
parent
98d2f8d6bb
commit
06b601c41b
|
@ -1280,7 +1280,6 @@ class Client {
|
|||
roomAccountData: {},
|
||||
client: this,
|
||||
);
|
||||
newRoom.restoreGroupSessionKeys();
|
||||
rooms.insert(position, newRoom);
|
||||
}
|
||||
// If the membership is "leave" then remove the item and stop here
|
||||
|
@ -1414,6 +1413,7 @@ class Client {
|
|||
.containsKey(toDeviceEvent.content['requesting_device_id'])) {
|
||||
deviceKeys = userDeviceKeys[toDeviceEvent.sender]
|
||||
.deviceKeys[toDeviceEvent.content['requesting_device_id']];
|
||||
await room.loadInboundGroupSessionKey(sessionId);
|
||||
if (room.inboundGroupSessions.containsKey(sessionId)) {
|
||||
final roomKeyRequest =
|
||||
RoomKeyRequest.fromToDeviceEvent(toDeviceEvent, this);
|
||||
|
@ -1558,11 +1558,6 @@ class Client {
|
|||
await f();
|
||||
}
|
||||
});
|
||||
rooms.forEach((Room room) {
|
||||
if (room.encrypted) {
|
||||
room.clearOutboundGroupSession();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
print('[LibOlm] Unable to update user device keys: ' + e.toString());
|
||||
}
|
||||
|
|
|
@ -92,24 +92,27 @@ class Database extends _$Database {
|
|||
return await dbGetInboundGroupSessionKeys(clientId, roomId).get();
|
||||
}
|
||||
|
||||
Future<DbInboundGroupSession> getDbInboundGroupSession(int clientId, String roomId, String sessionId) async {
|
||||
final res = await dbGetInboundGroupSessionKey(clientId, roomId, sessionId).get();
|
||||
if (res.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return res.first;
|
||||
}
|
||||
|
||||
Future<List<sdk.Room>> getRoomList(sdk.Client client, {bool onlyLeft = false}) async {
|
||||
final res = await (select(rooms)..where((t) => onlyLeft
|
||||
? t.membership.equals('leave')
|
||||
: t.membership.equals('leave').not())).get();
|
||||
final resStates = await getAllRoomStates(client.id).get();
|
||||
final resAccountData = await getAllRoomAccountData(client.id).get();
|
||||
final resOutboundGroupSessions = await getAllOutboundGroupSessions(client.id).get();
|
||||
final resInboundGroupSessions = await getAllInboundGroupSessions(client.id).get();
|
||||
final roomList = <sdk.Room>[];
|
||||
for (final r in res) {
|
||||
final outboundGroupSession = resOutboundGroupSessions.where((rs) => rs.roomId == r.roomId);
|
||||
final room = await sdk.Room.getRoomFromTableRow(
|
||||
r,
|
||||
client,
|
||||
states: resStates.where((rs) => rs.roomId == r.roomId),
|
||||
roomAccountData: resAccountData.where((rs) => rs.roomId == r.roomId),
|
||||
outboundGroupSession: outboundGroupSession.isEmpty ? false : outboundGroupSession.first,
|
||||
inboundGroupSessions: resInboundGroupSessions.where((rs) => rs.roomId == r.roomId),
|
||||
);
|
||||
roomList.add(room);
|
||||
}
|
||||
|
|
|
@ -4831,6 +4831,20 @@ abstract class _$Database extends GeneratedDatabase {
|
|||
);
|
||||
}
|
||||
|
||||
Selectable<DbInboundGroupSession> dbGetInboundGroupSessionKey(
|
||||
int client_id, String room_id, String session_id) {
|
||||
return customSelect(
|
||||
'SELECT * FROM inbound_group_sessions WHERE client_id = :client_id AND room_id = :room_id AND session_id = :session_id',
|
||||
variables: [
|
||||
Variable.withInt(client_id),
|
||||
Variable.withString(room_id),
|
||||
Variable.withString(session_id)
|
||||
],
|
||||
readsFrom: {
|
||||
inboundGroupSessions
|
||||
}).map(_rowToDbInboundGroupSession);
|
||||
}
|
||||
|
||||
Selectable<DbInboundGroupSession> dbGetInboundGroupSessionKeys(
|
||||
int client_id, String room_id) {
|
||||
return customSelect(
|
||||
|
|
|
@ -158,6 +158,7 @@ getAllOutboundGroupSessions: SELECT * FROM outbound_group_sessions WHERE client_
|
|||
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);
|
||||
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;
|
||||
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) VALUES (:client_id, :room_id, :session_id, :pickle, :content, :indexes);
|
||||
|
|
|
@ -414,6 +414,10 @@ class Event {
|
|||
return await timeline.getEventById(replyEventId);
|
||||
}
|
||||
|
||||
Future<void> loadSession() {
|
||||
return room.loadInboundGroupSessionKeyForEvent(this);
|
||||
}
|
||||
|
||||
/// Trys to decrypt this event. Returns a m.bad.encrypted event
|
||||
/// if it fails and does nothing if the event was not encrypted.
|
||||
Event get decrypted => room.decryptGroupMessage(this);
|
||||
|
|
|
@ -201,7 +201,7 @@ class Room {
|
|||
/// "session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8LlfJL7qNBEY..."
|
||||
/// }
|
||||
Map<String, SessionKey> get inboundGroupSessions => _inboundGroupSessions;
|
||||
Map<String, SessionKey> _inboundGroupSessions = {};
|
||||
final _inboundGroupSessions = <String, SessionKey>{};
|
||||
|
||||
/// Add a new session key to the [sessionKeys].
|
||||
void setInboundGroupSession(String sessionId, Map<String, dynamic> content,
|
||||
|
@ -228,12 +228,10 @@ class Room {
|
|||
indexes: {},
|
||||
key: client.userID,
|
||||
);
|
||||
if (_fullyRestored) {
|
||||
client.database?.storeInboundGroupSession(client.id, id, sessionId,
|
||||
inboundGroupSession.pickle(client.userID), json.encode(content),
|
||||
json.encode({}),
|
||||
);
|
||||
}
|
||||
_tryAgainDecryptLastMessage();
|
||||
onSessionKeyReceived.add(sessionId);
|
||||
}
|
||||
|
@ -1036,51 +1034,6 @@ class Room {
|
|||
return;
|
||||
}
|
||||
|
||||
Future<void> restoreGroupSessionKeys({
|
||||
dynamic outboundGroupSession, // DbOutboundGroupSession, optionally as future
|
||||
dynamic inboundGroupSessions, // DbSessionKey, as iterator and optionally as future
|
||||
}) async {
|
||||
// Restore the inbound and outbound session keys
|
||||
if (client.encryptionEnabled && client.database != null) {
|
||||
outboundGroupSession ??= client.database.getDbOutboundGroupSession(client.id, id);
|
||||
inboundGroupSessions ??= client.database.getDbInboundGroupSessions(client.id, id);
|
||||
if (outboundGroupSession is Future) {
|
||||
outboundGroupSession = await outboundGroupSession;
|
||||
}
|
||||
if (inboundGroupSessions is Future) {
|
||||
inboundGroupSessions = await inboundGroupSessions;
|
||||
}
|
||||
if (outboundGroupSession != false && outboundGroupSession != null) {
|
||||
try {
|
||||
_outboundGroupSession = olm.OutboundGroupSession();
|
||||
_outboundGroupSession.unpickle(
|
||||
client.userID, outboundGroupSession.pickle);
|
||||
} catch (e) {
|
||||
_outboundGroupSession = null;
|
||||
print('[LibOlm] Unable to unpickle outboundGroupSession: ' +
|
||||
e.toString());
|
||||
}
|
||||
_outboundGroupSessionDevices =
|
||||
List<String>.from(json.decode(outboundGroupSession.deviceIds));
|
||||
}
|
||||
if (inboundGroupSessions?.isNotEmpty ?? false) {
|
||||
_inboundGroupSessions ??= {};
|
||||
for (final sessionKey in inboundGroupSessions) {
|
||||
try {
|
||||
_inboundGroupSessions[sessionKey.sessionId] = SessionKey.fromDb(sessionKey, client.userID);
|
||||
} catch (e) {
|
||||
print('[LibOlm] Could not unpickle inboundGroupSession: ' + e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_tryAgainDecryptLastMessage();
|
||||
_fullyRestored = true;
|
||||
return;
|
||||
}
|
||||
|
||||
bool _fullyRestored = false;
|
||||
|
||||
/// Returns a Room from a json String which comes normally from the store. If the
|
||||
/// state are also given, the method will await them.
|
||||
static Future<Room> getRoomFromTableRow(
|
||||
|
@ -1089,8 +1042,6 @@ class Room {
|
|||
{
|
||||
dynamic states, // DbRoomState, as iterator and optionally as future
|
||||
dynamic roomAccountData, // DbRoomAccountData, as iterator and optionally as future
|
||||
dynamic outboundGroupSession, // DbOutboundGroupSession, optionally as future
|
||||
dynamic inboundGroupSessions, // DbSessionKey, as iterator and optionally as future
|
||||
}) async {
|
||||
final newRoom = Room(
|
||||
id: row.roomId,
|
||||
|
@ -1137,12 +1088,6 @@ class Room {
|
|||
}
|
||||
newRoom.roomAccountData = newRoomAccountData;
|
||||
|
||||
// Restore the inbound and outbound session keys
|
||||
await newRoom.restoreGroupSessionKeys(
|
||||
outboundGroupSession: outboundGroupSession,
|
||||
inboundGroupSessions: inboundGroupSessions,
|
||||
);
|
||||
|
||||
return newRoom;
|
||||
}
|
||||
|
||||
|
@ -1163,6 +1108,7 @@ class Room {
|
|||
for (var i = 0; i < events.length; i++) {
|
||||
if (events[i].type == EventTypes.Encrypted &&
|
||||
events[i].content['body'] == DecryptError.UNKNOWN_SESSION) {
|
||||
await events[i].loadSession();
|
||||
events[i] = events[i].decrypted;
|
||||
if (events[i].type != EventTypes.Encrypted) {
|
||||
await client.database.storeEventUpdate(client.id,
|
||||
|
@ -1742,6 +1688,30 @@ class Room {
|
|||
return deviceKeys;
|
||||
}
|
||||
|
||||
bool _restoredOutboundGroupSession = false;
|
||||
|
||||
Future<void> restoreOutboundGroupSession() async {
|
||||
if (_restoredOutboundGroupSession || client.database == null) {
|
||||
return;
|
||||
}
|
||||
final outboundSession = await client.database.getDbOutboundGroupSession(client.id, id);
|
||||
if (outboundSession != null) {
|
||||
try {
|
||||
_outboundGroupSession = olm.OutboundGroupSession();
|
||||
_outboundGroupSession.unpickle(
|
||||
client.userID, outboundSession.pickle);
|
||||
_outboundGroupSessionDevices =
|
||||
List<String>.from(json.decode(outboundSession.deviceIds));
|
||||
} catch (e) {
|
||||
_outboundGroupSession = null;
|
||||
_outboundGroupSessionDevices = null;
|
||||
print('[LibOlm] Unable to unpickle outboundGroupSession: ' +
|
||||
e.toString());
|
||||
}
|
||||
}
|
||||
_restoredOutboundGroupSession = true;
|
||||
}
|
||||
|
||||
/// Encrypts the given json payload and creates a send-ready m.room.encrypted
|
||||
/// payload. This will create a new outgoingGroupSession if necessary.
|
||||
Future<Map<String, dynamic>> encryptGroupMessagePayload(
|
||||
|
@ -1751,6 +1721,13 @@ class Room {
|
|||
if (encryptionAlgorithm != 'm.megolm.v1.aes-sha2') {
|
||||
throw ('Unknown encryption algorithm');
|
||||
}
|
||||
if (!_restoredOutboundGroupSession && client.database != null) {
|
||||
// try to restore an outbound group session from the database
|
||||
await restoreOutboundGroupSession();
|
||||
}
|
||||
// and clear the outbound session, if it needs clearing
|
||||
await clearOutboundGroupSession();
|
||||
// create a new one if none exists...
|
||||
if (_outboundGroupSession == null) {
|
||||
await createOutboundGroupSession();
|
||||
}
|
||||
|
@ -1772,6 +1749,30 @@ class Room {
|
|||
return encryptedPayload;
|
||||
}
|
||||
|
||||
Future<void> loadInboundGroupSessionKey(String sessionId) async {
|
||||
if (sessionId == null || inboundGroupSessions.containsKey(sessionId)) return; // nothing to do
|
||||
final session = await client.database.getDbInboundGroupSession(client.id, id, sessionId);
|
||||
if (session == null) return; // no session found
|
||||
try {
|
||||
_inboundGroupSessions[sessionId] = SessionKey.fromDb(session, client.userID);
|
||||
} catch (e) {
|
||||
print('[LibOlm] Could not unpickle inboundGroupSession: ' + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadInboundGroupSessionKeyForEvent(Event event) async {
|
||||
if (client.database == null) return; // nothing to do, no database
|
||||
if (event.type != EventTypes.Encrypted) return;
|
||||
if (!client.encryptionEnabled) {
|
||||
throw (DecryptError.NOT_ENABLED);
|
||||
}
|
||||
if (event.content['algorithm'] != 'm.megolm.v1.aes-sha2') {
|
||||
throw (DecryptError.UNKNOWN_ALGORITHM);
|
||||
}
|
||||
final String sessionId = event.content['session_id'];
|
||||
return loadInboundGroupSessionKey(sessionId);
|
||||
}
|
||||
|
||||
/// Decrypts the given [event] with one of the available ingoingGroupSessions.
|
||||
/// Returns a m.bad.encrypted event if it fails and does nothing if the event
|
||||
/// was not encrypted.
|
||||
|
|
|
@ -53,7 +53,7 @@ class EventUpdate {
|
|||
var decrpytedEvent =
|
||||
room.decryptGroupMessage(Event.fromJson(content, room, sortOrder));
|
||||
return EventUpdate(
|
||||
eventType: eventType,
|
||||
eventType: decrpytedEvent.typeKey,
|
||||
roomID: roomID,
|
||||
type: type,
|
||||
content: decrpytedEvent.toJson(),
|
||||
|
|
|
@ -116,6 +116,19 @@ class Timeline {
|
|||
try {
|
||||
if (eventUpdate.roomID != room.id) return;
|
||||
|
||||
// try to decrypt the event first, if needed
|
||||
if (eventUpdate.eventType == 'm.room.encrypted' && room.client.database != null) {
|
||||
try {
|
||||
await room.loadInboundGroupSessionKey(eventUpdate.content['content']['session_id']);
|
||||
eventUpdate = eventUpdate.decrypt(room);
|
||||
if (eventUpdate.eventType != 'm.room.encrypted') {
|
||||
await room.client.database.storeEventUpdate(room.client.id, eventUpdate);
|
||||
}
|
||||
} catch (err) {
|
||||
print('[WARNING] (_handleEventUpdate) Failed to decrypt event: ${err.toString()}');
|
||||
}
|
||||
}
|
||||
|
||||
if (eventUpdate.type == 'timeline' || eventUpdate.type == 'history') {
|
||||
// Redaction events are handled as modification for existing events.
|
||||
if (eventUpdate.eventType == 'm.room.redaction') {
|
||||
|
|
|
@ -16,6 +16,7 @@ class RoomKeyRequest extends ToDeviceEvent {
|
|||
|
||||
Future<void> forwardKey() async {
|
||||
var room = this.room;
|
||||
await room.loadInboundGroupSessionKey(content['body']['session_id']);
|
||||
final session = room.inboundGroupSessions[content['body']['session_id']];
|
||||
var forwardedKeys = <dynamic>[client.identityKey];
|
||||
for (final key in session.forwardingCurve25519KeyChain) {
|
||||
|
|
|
@ -667,6 +667,7 @@ void main() {
|
|||
expect(client2.deviceID, client1.deviceID);
|
||||
expect(client2.deviceName, client1.deviceName);
|
||||
if (client2.encryptionEnabled) {
|
||||
await client2.rooms[1].restoreOutboundGroupSession();
|
||||
expect(client2.pickledOlmAccount, client1.pickledOlmAccount);
|
||||
expect(json.encode(client2.rooms[1].inboundGroupSessions[sessionKey]),
|
||||
json.encode(client1.rooms[1].inboundGroupSessions[sessionKey]));
|
||||
|
|
Loading…
Reference in a new issue