fix: Various small e2ee fixes
This commit is contained in:
parent
d93f420d38
commit
ccd03ecd22
|
|
@ -30,6 +30,7 @@ import '../matrix_api/utils/logs.dart';
|
||||||
import '../src/utils/run_in_background.dart';
|
import '../src/utils/run_in_background.dart';
|
||||||
import '../src/utils/run_in_root.dart';
|
import '../src/utils/run_in_root.dart';
|
||||||
import '../matrix_api/utils/try_get_map_extension.dart';
|
import '../matrix_api/utils/try_get_map_extension.dart';
|
||||||
|
import '../matrix_api/utils/map_copy_extension.dart';
|
||||||
|
|
||||||
const MEGOLM_KEY = EventTypes.MegolmBackup;
|
const MEGOLM_KEY = EventTypes.MegolmBackup;
|
||||||
|
|
||||||
|
|
@ -743,7 +744,8 @@ class KeyManager {
|
||||||
}
|
}
|
||||||
if (event.content['action'] == 'request') {
|
if (event.content['action'] == 'request') {
|
||||||
// we are *receiving* a request
|
// we are *receiving* a request
|
||||||
Logs().i('[KeyManager] Received key sharing request...');
|
Logs().i(
|
||||||
|
'[KeyManager] Received key sharing request from ${event.sender}:${event.content['requesting_device_id']}...');
|
||||||
if (!event.content.containsKey('body')) {
|
if (!event.content.containsKey('body')) {
|
||||||
Logs().i('[KeyManager] No body, doing nothing');
|
Logs().i('[KeyManager] No body, doing nothing');
|
||||||
return; // no body
|
return; // no body
|
||||||
|
|
@ -954,10 +956,10 @@ class RoomKeyRequest extends ToDeviceEvent {
|
||||||
keyManager.incomingShareRequests.remove(request.requestId);
|
keyManager.incomingShareRequests.remove(request.requestId);
|
||||||
return; // request is canceled, don't send anything
|
return; // request is canceled, don't send anything
|
||||||
}
|
}
|
||||||
var room = this.room;
|
final room = this.room;
|
||||||
final session = await keyManager.loadInboundGroupSession(
|
final session = await keyManager.loadInboundGroupSession(
|
||||||
room.id, request.sessionId, request.senderKey);
|
room.id, request.sessionId, request.senderKey);
|
||||||
var message = session.content;
|
final message = session.content.copy();
|
||||||
message['forwarding_curve25519_key_chain'] =
|
message['forwarding_curve25519_key_chain'] =
|
||||||
List<String>.from(session.forwardingCurve25519KeyChain);
|
List<String>.from(session.forwardingCurve25519KeyChain);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -227,9 +227,7 @@ class OlmManager {
|
||||||
if (client.database == null) {
|
if (client.database == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!_olmSessions.containsKey(session.identityKey)) {
|
_olmSessions[session.identityKey] ??= [];
|
||||||
_olmSessions[session.identityKey] = [];
|
|
||||||
}
|
|
||||||
final ix = _olmSessions[session.identityKey]
|
final ix = _olmSessions[session.identityKey]
|
||||||
.indexWhere((s) => s.sessionId == session.sessionId);
|
.indexWhere((s) => s.sessionId == session.sessionId);
|
||||||
if (ix == -1) {
|
if (ix == -1) {
|
||||||
|
|
@ -264,19 +262,21 @@ class OlmManager {
|
||||||
if (type != 0 && type != 1) {
|
if (type != 0 && type != 1) {
|
||||||
throw ('Unknown message type');
|
throw ('Unknown message type');
|
||||||
}
|
}
|
||||||
var existingSessions = olmSessions[senderKey];
|
final existingSessions = olmSessions[senderKey];
|
||||||
|
final updateSessionUsage = (OlmSession session) => runInRoot(() {
|
||||||
|
session.lastReceived = DateTime.now();
|
||||||
|
storeOlmSession(session);
|
||||||
|
});
|
||||||
if (existingSessions != null) {
|
if (existingSessions != null) {
|
||||||
for (var session in existingSessions) {
|
for (var session in existingSessions) {
|
||||||
if (type == 0 && session.session.matches_inbound(body) == true) {
|
if (type == 0 && session.session.matches_inbound(body) == true) {
|
||||||
plaintext = session.session.decrypt(type, body);
|
plaintext = session.session.decrypt(type, body);
|
||||||
session.lastReceived = DateTime.now();
|
updateSessionUsage(session);
|
||||||
storeOlmSession(session);
|
|
||||||
break;
|
break;
|
||||||
} else if (type == 1) {
|
} else if (type == 1) {
|
||||||
try {
|
try {
|
||||||
plaintext = session.session.decrypt(type, body);
|
plaintext = session.session.decrypt(type, body);
|
||||||
session.lastReceived = DateTime.now();
|
updateSessionUsage(session);
|
||||||
storeOlmSession(session);
|
|
||||||
break;
|
break;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
plaintext = null;
|
plaintext = null;
|
||||||
|
|
@ -295,13 +295,13 @@ class OlmManager {
|
||||||
_olmAccount.remove_one_time_keys(newSession);
|
_olmAccount.remove_one_time_keys(newSession);
|
||||||
client.database?.updateClientKeys(pickledOlmAccount, client.id);
|
client.database?.updateClientKeys(pickledOlmAccount, client.id);
|
||||||
plaintext = newSession.decrypt(type, body);
|
plaintext = newSession.decrypt(type, body);
|
||||||
storeOlmSession(OlmSession(
|
runInRoot(() => storeOlmSession(OlmSession(
|
||||||
key: client.userID,
|
key: client.userID,
|
||||||
identityKey: senderKey,
|
identityKey: senderKey,
|
||||||
sessionId: newSession.session_id(),
|
sessionId: newSession.session_id(),
|
||||||
session: newSession,
|
session: newSession,
|
||||||
lastReceived: DateTime.now(),
|
lastReceived: DateTime.now(),
|
||||||
));
|
)));
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
newSession?.free();
|
newSession?.free();
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|
@ -345,6 +345,21 @@ class OlmManager {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<OlmSession>> getOlmSessions(String senderKey) async {
|
||||||
|
var sess = olmSessions[senderKey];
|
||||||
|
if (sess == null || sess.isEmpty) {
|
||||||
|
final sessions = await getOlmSessionsFromDatabase(senderKey);
|
||||||
|
if (sessions.isEmpty) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
sess = _olmSessions[senderKey] = sessions;
|
||||||
|
}
|
||||||
|
sess.sort((a, b) => a.lastReceived == b.lastReceived
|
||||||
|
? a.sessionId.compareTo(b.sessionId)
|
||||||
|
: b.lastReceived.compareTo(a.lastReceived));
|
||||||
|
return sess;
|
||||||
|
}
|
||||||
|
|
||||||
final Map<String, DateTime> _restoredOlmSessionsTime = {};
|
final Map<String, DateTime> _restoredOlmSessionsTime = {};
|
||||||
|
|
||||||
Future<void> restoreOlmSession(String userId, String senderKey) async {
|
Future<void> restoreOlmSession(String userId, String senderKey) async {
|
||||||
|
|
@ -375,15 +390,8 @@ class OlmManager {
|
||||||
}
|
}
|
||||||
final senderKey = event.content['sender_key'];
|
final senderKey = event.content['sender_key'];
|
||||||
final loadFromDb = () async {
|
final loadFromDb = () async {
|
||||||
if (client.database == null) {
|
final sessions = await getOlmSessions(senderKey);
|
||||||
return false;
|
return sessions.isNotEmpty;
|
||||||
}
|
|
||||||
final sessions = await getOlmSessionsFromDatabase(senderKey);
|
|
||||||
if (sessions.isEmpty) {
|
|
||||||
return false; // okay, can't do anything
|
|
||||||
}
|
|
||||||
_olmSessions[senderKey] = sessions;
|
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
if (!_olmSessions.containsKey(senderKey)) {
|
if (!_olmSessions.containsKey(senderKey)) {
|
||||||
await loadFromDb();
|
await loadFromDb();
|
||||||
|
|
@ -406,6 +414,9 @@ class OlmManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> startOutgoingOlmSessions(List<DeviceKeys> deviceKeys) async {
|
Future<void> startOutgoingOlmSessions(List<DeviceKeys> deviceKeys) async {
|
||||||
|
Logs().v(
|
||||||
|
'[OlmManager] Starting session with ${deviceKeys.length} devices...');
|
||||||
|
|
||||||
var requestingKeysFrom = <String, Map<String, String>>{};
|
var requestingKeysFrom = <String, Map<String, String>>{};
|
||||||
for (var device in deviceKeys) {
|
for (var device in deviceKeys) {
|
||||||
if (requestingKeysFrom[device.userId] == null) {
|
if (requestingKeysFrom[device.userId] == null) {
|
||||||
|
|
@ -429,6 +440,7 @@ class OlmManager {
|
||||||
if (!deviceKey.checkJsonSignature(fingerprintKey, userId, deviceId)) {
|
if (!deviceKey.checkJsonSignature(fingerprintKey, userId, deviceId)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
Logs().v('[OlmManager] Starting session with ${userId}:${deviceId}');
|
||||||
var session = olm.Session();
|
var session = olm.Session();
|
||||||
try {
|
try {
|
||||||
session.create_outbound(_olmAccount, identityKey, deviceKey['key']);
|
session.create_outbound(_olmAccount, identityKey, deviceKey['key']);
|
||||||
|
|
@ -452,17 +464,11 @@ class OlmManager {
|
||||||
|
|
||||||
Future<Map<String, dynamic>> encryptToDeviceMessagePayload(
|
Future<Map<String, dynamic>> encryptToDeviceMessagePayload(
|
||||||
DeviceKeys device, String type, Map<String, dynamic> payload) async {
|
DeviceKeys device, String type, Map<String, dynamic> payload) async {
|
||||||
var sess = olmSessions[device.curve25519Key];
|
final sess = await getOlmSessions(device.curve25519Key);
|
||||||
if (sess == null || sess.isEmpty) {
|
if (sess.isEmpty) {
|
||||||
final sessions = await getOlmSessionsFromDatabase(device.curve25519Key);
|
throw ('No olm session found for ${device.userId}:${device.deviceId}');
|
||||||
if (sessions.isEmpty) {
|
|
||||||
throw ('No olm session found');
|
|
||||||
}
|
|
||||||
sess = _olmSessions[device.curve25519Key] = sessions;
|
|
||||||
}
|
}
|
||||||
sess.sort((a, b) => a.lastReceived == b.lastReceived
|
|
||||||
? a.sessionId.compareTo(b.sessionId)
|
|
||||||
: b.lastReceived.compareTo(a.lastReceived));
|
|
||||||
final fullPayload = {
|
final fullPayload = {
|
||||||
'type': type,
|
'type': type,
|
||||||
'content': payload,
|
'content': payload,
|
||||||
|
|
@ -493,18 +499,13 @@ class OlmManager {
|
||||||
// first check if any of our sessions we want to encrypt for are in the database
|
// first check if any of our sessions we want to encrypt for are in the database
|
||||||
if (client.database != null) {
|
if (client.database != null) {
|
||||||
for (final device in deviceKeys) {
|
for (final device in deviceKeys) {
|
||||||
if (!olmSessions.containsKey(device.curve25519Key)) {
|
await getOlmSessions(device.curve25519Key);
|
||||||
final sessions =
|
|
||||||
await getOlmSessionsFromDatabase(device.curve25519Key);
|
|
||||||
if (sessions.isNotEmpty) {
|
|
||||||
_olmSessions[device.curve25519Key] = sessions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final deviceKeysWithoutSession = List<DeviceKeys>.from(deviceKeys);
|
final deviceKeysWithoutSession = List<DeviceKeys>.from(deviceKeys);
|
||||||
deviceKeysWithoutSession.removeWhere((DeviceKeys deviceKeys) =>
|
deviceKeysWithoutSession.removeWhere((DeviceKeys deviceKeys) =>
|
||||||
olmSessions.containsKey(deviceKeys.curve25519Key));
|
olmSessions.containsKey(deviceKeys.curve25519Key) &&
|
||||||
|
olmSessions[deviceKeys.curve25519Key].isNotEmpty);
|
||||||
if (deviceKeysWithoutSession.isNotEmpty) {
|
if (deviceKeysWithoutSession.isNotEmpty) {
|
||||||
await startOutgoingOlmSessions(deviceKeysWithoutSession);
|
await startOutgoingOlmSessions(deviceKeysWithoutSession);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1428,7 +1428,17 @@ sort order of ${prevState.sortOrder}. This should never happen...''');
|
||||||
// Always trust the own device
|
// Always trust the own device
|
||||||
entry.setDirectVerified(true);
|
entry.setDirectVerified(true);
|
||||||
}
|
}
|
||||||
} else {
|
if (database != null) {
|
||||||
|
dbActions.add(() => database.storeUserDeviceKey(
|
||||||
|
id,
|
||||||
|
userId,
|
||||||
|
deviceId,
|
||||||
|
json.encode(entry.toJson()),
|
||||||
|
entry.directVerified,
|
||||||
|
entry.blocked,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if (oldKeys.containsKey(deviceId)) {
|
||||||
// This shouldn't ever happen. The same device ID has gotten
|
// This shouldn't ever happen. The same device ID has gotten
|
||||||
// a new public key. So we ignore the update. TODO: ask krille
|
// a new public key. So we ignore the update. TODO: ask krille
|
||||||
// if we should instead use the new key with unknown verified / blocked status
|
// if we should instead use the new key with unknown verified / blocked status
|
||||||
|
|
@ -1438,16 +1448,6 @@ sort order of ${prevState.sortOrder}. This should never happen...''');
|
||||||
} else {
|
} else {
|
||||||
Logs().w('Invalid device ${entry.userId}:${entry.deviceId}');
|
Logs().w('Invalid device ${entry.userId}:${entry.deviceId}');
|
||||||
}
|
}
|
||||||
if (database != null) {
|
|
||||||
dbActions.add(() => database.storeUserDeviceKey(
|
|
||||||
id,
|
|
||||||
userId,
|
|
||||||
deviceId,
|
|
||||||
json.encode(entry.toJson()),
|
|
||||||
entry.directVerified,
|
|
||||||
entry.blocked,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// delete old/unused entries
|
// delete old/unused entries
|
||||||
if (database != null) {
|
if (database != null) {
|
||||||
|
|
@ -1571,8 +1571,7 @@ sort order of ${prevState.sortOrder}. This should never happen...''');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends an encrypted [message] of this [type] to these [deviceKeys]. To send
|
/// Sends an encrypted [message] of this [type] to these [deviceKeys].
|
||||||
/// the request to all devices of the current user, pass an empty list to [deviceKeys].
|
|
||||||
Future<void> sendToDeviceEncrypted(
|
Future<void> sendToDeviceEncrypted(
|
||||||
List<DeviceKeys> deviceKeys,
|
List<DeviceKeys> deviceKeys,
|
||||||
String eventType,
|
String eventType,
|
||||||
|
|
@ -1586,7 +1585,7 @@ sort order of ${prevState.sortOrder}. This should never happen...''');
|
||||||
if (deviceKeys.isNotEmpty) {
|
if (deviceKeys.isNotEmpty) {
|
||||||
deviceKeys.removeWhere((DeviceKeys deviceKeys) =>
|
deviceKeys.removeWhere((DeviceKeys deviceKeys) =>
|
||||||
deviceKeys.blocked ||
|
deviceKeys.blocked ||
|
||||||
deviceKeys.deviceId == deviceID ||
|
(deviceKeys.userId == userID && deviceKeys.deviceId == deviceID) ||
|
||||||
(onlyVerified && !deviceKeys.verified));
|
(onlyVerified && !deviceKeys.verified));
|
||||||
if (deviceKeys.isEmpty) return;
|
if (deviceKeys.isEmpty) return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue