fix: Cleanup nullsafe encryption a bit

This commit is contained in:
Nicolas Werner 2021-09-27 12:17:35 +02:00
parent da80658c09
commit 1c838e3be8
9 changed files with 132 additions and 162 deletions

View File

@ -149,8 +149,7 @@ class Encryption {
runInRoot(() => keyVerificationManager.handleEventUpdate(update));
}
if (update.content['sender'] == client.userID &&
(!update.content.containsKey('unsigned') ||
!update.content['unsigned'].containsKey('transaction_id'))) {
update.content['unsigned']?['transaction_id'] == null) {
// maybe we need to re-try SSSS secrets
// ignore: unawaited_futures
runInRoot(() => ssss.periodicallyRequestMissingCache());
@ -375,17 +374,15 @@ class Encryption {
Future<void> autovalidateMasterOwnKey() async {
// check if we can set our own master key as verified, if it isn't yet
final masterKey = client.userDeviceKeys[client.userID]?.masterKey;
if (client.database != null &&
client.userDeviceKeys.containsKey(client.userID)) {
final masterKey = client.userDeviceKeys[client.userID]!.masterKey;
if (masterKey != null &&
masterKey != null &&
!masterKey.directVerified &&
masterKey
.hasValidSignatureChain(onlyValidateUserIds: {client.userID})) {
await masterKey.setVerified(true);
}
}
}
// this method is responsible for all background tasks, such as uploading online key backups
bool _backgroundTasksRunning = true;

View File

@ -146,10 +146,10 @@ class KeyManager {
newSession.dispose();
return;
}
if (!_inboundGroupSessions.containsKey(roomId)) {
_inboundGroupSessions[roomId] = <String, SessionKey>{};
}
_inboundGroupSessions[roomId]![sessionId] = newSession;
final roomInboundGroupSessions =
_inboundGroupSessions[roomId] ??= <String, SessionKey>{};
roomInboundGroupSessions[sessionId] = newSession;
if (!client.isLogged() || client.encryption == null) {
return;
}
@ -189,9 +189,8 @@ class KeyManager {
SessionKey? getInboundGroupSession(
String roomId, String sessionId, String senderKey,
{bool otherRooms = true}) {
if (_inboundGroupSessions.containsKey(roomId) &&
_inboundGroupSessions[roomId]!.containsKey(sessionId)) {
final sess = _inboundGroupSessions[roomId]![sessionId]!;
final sess = _inboundGroupSessions[roomId]?[sessionId];
if (sess != null) {
if (sess.senderKey != senderKey && sess.senderKey.isNotEmpty) {
return null;
}
@ -202,8 +201,8 @@ class KeyManager {
}
// search if this session id is *somehow* found in another room
for (final val in _inboundGroupSessions.values) {
if (val.containsKey(sessionId)) {
final sess = val[sessionId]!;
final sess = val[sessionId];
if (sess != null) {
if (sess.senderKey != senderKey && sess.senderKey.isNotEmpty) {
return null;
}
@ -231,9 +230,8 @@ class KeyManager {
/// Loads an inbound group session
Future<SessionKey?> loadInboundGroupSession(
String roomId, String sessionId, String senderKey) async {
if (_inboundGroupSessions.containsKey(roomId) &&
_inboundGroupSessions[roomId]!.containsKey(sessionId)) {
final sess = _inboundGroupSessions[roomId]![sessionId]!;
final sess = _inboundGroupSessions[roomId]?[sessionId];
if (sess != null) {
if (sess.senderKey != senderKey && sess.senderKey.isNotEmpty) {
return null; // sender keys do not match....better not do anything
}
@ -244,16 +242,15 @@ class KeyManager {
if (session == null) {
return null;
}
final sess = SessionKey.fromDb(session, client.userID);
if (!_inboundGroupSessions.containsKey(roomId)) {
_inboundGroupSessions[roomId] = <String, SessionKey>{};
}
if (!sess.isValid ||
sess.senderKey.isEmpty ||
sess.senderKey != senderKey) {
final dbSess = SessionKey.fromDb(session, client.userID);
final roomInboundGroupSessions =
_inboundGroupSessions[roomId] ??= <String, SessionKey>{};
if (!dbSess.isValid ||
dbSess.senderKey.isEmpty ||
dbSess.senderKey != senderKey) {
return null;
}
_inboundGroupSessions[roomId]![sessionId] = sess;
roomInboundGroupSessions[sessionId] = dbSess;
return sess;
}
@ -261,14 +258,13 @@ class KeyManager {
List<DeviceKeys> deviceKeys) {
final deviceKeyIds = <String, Map<String, bool>>{};
for (final device in deviceKeys) {
if (device.deviceId == null) {
final deviceId = device.deviceId;
if (deviceId == null) {
Logs().w('[KeyManager] ignoring device without deviceid');
continue;
}
if (!deviceKeyIds.containsKey(device.userId)) {
deviceKeyIds[device.userId] = <String, bool>{};
}
deviceKeyIds[device.userId]![device.deviceId!] = !device.encryptToDevice;
final userDeviceKeyIds = deviceKeyIds[device.userId] ??= <String, bool>{};
userDeviceKeyIds[deviceId] = !device.encryptToDevice;
}
return deviceKeyIds;
}
@ -440,8 +436,9 @@ class KeyManager {
/// Creates an outbound group session for a given room id
Future<OutboundGroupSession> createOutboundGroupSession(String roomId) async {
if (_pendingNewOutboundGroupSessions.containsKey(roomId)) {
return _pendingNewOutboundGroupSessions[roomId]!;
final sess = _pendingNewOutboundGroupSessions[roomId];
if (sess != null) {
return sess;
}
_pendingNewOutboundGroupSessions[roomId] =
_createOutboundGroupSession(roomId);
@ -791,14 +788,12 @@ class KeyManager {
Logs().i('[KeyManager] No body, doing nothing');
return; // no body
}
if (!client.userDeviceKeys.containsKey(event.sender) ||
!client.userDeviceKeys[event.sender]!.deviceKeys
.containsKey(event.content['requesting_device_id'])) {
final device = client.userDeviceKeys[event.sender]
?.deviceKeys[event.content['requesting_device_id']];
if (device == null) {
Logs().i('[KeyManager] Device not found, doing nothing');
return; // device not found
}
final device = client.userDeviceKeys[event.sender]!
.deviceKeys[event.content['requesting_device_id']]!;
if (device.userId == client.userID &&
device.deviceId == client.deviceID) {
Logs().i('[KeyManager] Request is by ourself, ignoring');
@ -914,10 +909,8 @@ class KeyManager {
};
final data = <String, Map<String, Map<String, dynamic>>>{};
for (final device in request.devices) {
if (!data.containsKey(device.userId)) {
data[device.userId] = {};
}
data[device.userId]![device.deviceId!] = sendToDeviceMessage;
final userData = data[device.userId] ??= {};
userData[device.deviceId!] = sendToDeviceMessage;
}
await client.sendToDevice(
EventTypes.RoomKeyRequest,
@ -933,13 +926,10 @@ class KeyManager {
}
final String roomId = event.content['room_id'];
final String sessionId = event.content['session_id'];
if (client.userDeviceKeys.containsKey(event.sender) &&
client.userDeviceKeys[event.sender]!.deviceKeys
.containsKey(event.content['requesting_device_id'])) {
event.content['sender_claimed_ed25519_key'] = client
.userDeviceKeys[event.sender]!
.deviceKeys[event.content['requesting_device_id']]!
.ed25519Key;
final sender_ed25519 = client.userDeviceKeys[event.sender]
?.deviceKeys[event.content['requesting_device_id']]?.ed25519Key;
if (sender_ed25519 != null) {
event.content['sender_claimed_ed25519_key'] = sender_ed25519;
}
Logs().v('[KeyManager] Keeping room key');
setInboundGroupSession(roomId, sessionId,
@ -1044,9 +1034,8 @@ RoomKeys _generateUploadKeys(_GenerateUploadKeysArgs args) {
continue;
}
// create the room if it doesn't exist
if (!roomKeys.rooms.containsKey(sess.roomId)) {
roomKeys.rooms[sess.roomId] = RoomKeyBackup(sessions: {});
}
final roomKeyBackup =
roomKeys.rooms[sess.roomId] ??= RoomKeyBackup(sessions: {});
// generate the encrypted content
final payload = <String, dynamic>{
'algorithm': AlgorithmTypes.megolmV1AesSha2,
@ -1061,7 +1050,7 @@ RoomKeys _generateUploadKeys(_GenerateUploadKeysArgs args) {
// fetch the device, if available...
//final device = args.client.getUserDeviceKeysByCurve25519Key(sess.senderKey);
// aaaand finally add the session key to our payload
roomKeys.rooms[sess.roomId]!.sessions[sess.sessionId] = KeyBackupData(
roomKeyBackup.sessions[sess.sessionId] = KeyBackupData(
firstMessageIndex: sess.inboundGroupSession!.first_known_index(),
forwardedCount: sess.forwardingCurve25519KeyChain.length,
isVerified: dbSession.verified, //device?.verified ?? false,

View File

@ -411,12 +411,10 @@ class OlmManager {
}
}
final Map<String, dynamic> plainContent = json.decode(plaintext);
if (plainContent.containsKey('sender') &&
plainContent['sender'] != event.sender) {
if (plainContent['sender'] != event.sender) {
throw DecryptException(DecryptException.senderDoesntMatch);
}
if (plainContent.containsKey('recipient') &&
plainContent['recipient'] != client.userID) {
if (plainContent['recipient'] != client.userID) {
throw DecryptException(DecryptException.recipientDoesntMatch);
}
if (plainContent['recipient_keys'] is Map &&
@ -637,18 +635,15 @@ class OlmManager {
}
final deviceKeysWithoutSession = List<DeviceKeys>.from(deviceKeys);
deviceKeysWithoutSession.removeWhere((DeviceKeys deviceKeys) =>
olmSessions.containsKey(deviceKeys.curve25519Key) &&
olmSessions[deviceKeys.curve25519Key]!.isNotEmpty);
olmSessions[deviceKeys.curve25519Key]?.isNotEmpty ?? false);
if (deviceKeysWithoutSession.isNotEmpty) {
await startOutgoingOlmSessions(deviceKeysWithoutSession);
}
for (final device in deviceKeys) {
if (!data.containsKey(device.userId)) {
data[device.userId] = {};
}
final userData = data[device.userId] ??= {};
try {
data[device.userId]![device.deviceId!] =
await encryptToDeviceMessagePayload(device, type, payload,
userData[device.deviceId!] = await encryptToDeviceMessagePayload(
device, type, payload,
getFromDb: false);
} catch (e, s) {
Logs().w('[LibOlm] Error encrypting to-device event', e, s);

View File

@ -90,10 +90,11 @@ class Bootstrap {
// cache the secret analyzing so that we don't drop stuff a different client sets during bootstrapping
Map<String, Set<String>>? _secretsCache;
Map<String, Set<String>> analyzeSecrets() {
if (_secretsCache != null) {
final secretsCache = _secretsCache;
if (secretsCache != null) {
// deep-copy so that we can do modifications
final newSecrets = <String, Set<String>>{};
for (final s in _secretsCache!.entries) {
for (final s in secretsCache.entries) {
newSecrets[s.key] = Set<String>.from(s.value);
}
return newSecrets;
@ -147,11 +148,7 @@ class Bootstrap {
final usage = <String, int>{};
for (final keys in secrets.values) {
for (final key in keys) {
if (!usage.containsKey(key)) {
usage[key] = 1;
} else {
usage[key] = usage[key]! + 1;
}
usage.update(key, (i) => i++, ifAbsent: () => 1);
}
}
final entriesList = usage.entries.toList();
@ -315,14 +312,15 @@ class Bootstrap {
}
Future<void> openExistingSsss() async {
if (state != BootstrapState.openExistingSsss || newSsssKey == null) {
final newSsssKey_ = newSsssKey;
if (state != BootstrapState.openExistingSsss || newSsssKey_ == null) {
throw BootstrapBadStateException();
}
if (!newSsssKey!.isUnlocked) {
if (!newSsssKey_.isUnlocked) {
throw BootstrapBadStateException('Key not unlocked');
}
Logs().v('Maybe cache all...');
await newSsssKey!.maybeCacheAll();
await newSsssKey_.maybeCacheAll();
checkCrossSigning();
}
@ -477,12 +475,9 @@ class Bootstrap {
futures.add(
client.onSync.stream
.firstWhere((syncUpdate) =>
client.userDeviceKeys.containsKey(client.userID) &&
client.userDeviceKeys[client.userID]!.masterKey != null &&
client.userDeviceKeys[client.userID]!.masterKey!.ed25519Key !=
null &&
client.userDeviceKeys[client.userID]!.masterKey!.ed25519Key ==
masterKey!.publicKey)
masterKey?.publicKey != null &&
client.userDeviceKeys[client.userID]?.masterKey?.ed25519Key ==
masterKey?.publicKey)
.then((_) => Logs().v('New Master Key was created')),
);
}
@ -595,8 +590,10 @@ class Bootstrap {
if (state != BootstrapState.error) {
_state = newState;
}
if (onUpdate != null) {
onUpdate!();
final onUpdate_ = onUpdate;
if (onUpdate_ != null) {
onUpdate_();
}
}
}

View File

@ -227,10 +227,9 @@ class KeyVerification {
if (deviceId == '*') {
_deviceId = payload['from_device']; // gotta set the real device id
// and broadcast the cancel to the other devices
final devices = client.userDeviceKeys.containsKey(userId)
? List<DeviceKeys>.from(
client.userDeviceKeys[userId]!.deviceKeys.values)
: List<DeviceKeys>.from([]);
final devices = List<DeviceKeys>.from(
client.userDeviceKeys[userId]?.deviceKeys.values ??
Iterable.empty());
devices.removeWhere(
(d) => {deviceId, client.deviceID}.contains(d.deviceId));
final cancelPayload = <String, dynamic>{
@ -472,7 +471,8 @@ class KeyVerification {
Future<bool> Function(String, SignableKey) verifier) async {
_verifiedDevices = <SignableKey>[];
if (!client.userDeviceKeys.containsKey(userId)) {
final userDeviceKey = client.userDeviceKeys[userId];
if (userDeviceKey == null) {
await cancel('m.key_mismatch');
return;
}
@ -480,7 +480,7 @@ class KeyVerification {
final keyId = entry.key;
final verifyDeviceId = keyId.substring('ed25519:'.length);
final keyInfo = entry.value;
final key = client.userDeviceKeys[userId]!.getKey(verifyDeviceId);
final key = userDeviceKey.getKey(verifyDeviceId);
if (key != null) {
if (!(await verifier(keyInfo, key))) {
await cancel('m.key_mismatch');
@ -619,8 +619,10 @@ class KeyVerification {
if (state != KeyVerificationState.error) {
state = newState;
}
final onUpdate = this.onUpdate;
if (onUpdate != null) {
onUpdate!();
onUpdate();
}
}
}
@ -808,8 +810,8 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
}
Future<void> _sendAccept() async {
sas = olm.SAS();
commitment = _makeCommitment(sas!.get_pubkey(), startCanonicalJson);
final sas = this.sas ??= olm.SAS();
commitment = _makeCommitment(sas.get_pubkey(), startCanonicalJson);
await request.send(EventTypes.KeyVerificationAccept, {
'method': type,
'key_agreement_protocol': keyAgreementProtocol,
@ -906,9 +908,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
_calculateMac(encryption.fingerprintKey!, baseInfo + deviceKeyId);
keyList.add(deviceKeyId);
final masterKey = client.userDeviceKeys.containsKey(client.userID)
? client.userDeviceKeys[client.userID]!.masterKey
: null;
final masterKey = client.userDeviceKeys[client.userID]?.masterKey;
if (masterKey != null && masterKey.verified) {
// we have our own master key verified, let's send it!
final masterKeyId = 'ed25519:${masterKey.publicKey}';

View File

@ -49,7 +49,7 @@ class SessionKey {
late Map<String, String> senderClaimedKeys;
/// Sender curve25519 key
late String senderKey;
String senderKey;
/// Is this session valid?
bool get isValid => inboundGroupSession != null;

View File

@ -249,14 +249,9 @@ abstract class SignableKey extends MatrixSignableKey {
if (otherUserId == userId && keyId == identifier) {
continue;
}
SignableKey? key;
if (client.userDeviceKeys[otherUserId]!.deviceKeys.containsKey(keyId)) {
key = client.userDeviceKeys[otherUserId]!.deviceKeys[keyId];
} else if (client.userDeviceKeys[otherUserId]!.crossSigningKeys
.containsKey(keyId)) {
key = client.userDeviceKeys[otherUserId]!.crossSigningKeys[keyId];
}
final key = client.userDeviceKeys[otherUserId]?.deviceKeys[keyId] ??
client.userDeviceKeys[otherUserId]?.crossSigningKeys[keyId];
if (key == null) {
continue;
}
@ -272,25 +267,22 @@ abstract class SignableKey extends MatrixSignableKey {
}
var haveValidSignature = false;
var gotSignatureFromCache = false;
if (validSignatures != null &&
validSignatures!.containsKey(otherUserId) &&
validSignatures![otherUserId].containsKey(fullKeyId)) {
if (validSignatures![otherUserId][fullKeyId] == true) {
if (validSignatures?[otherUserId][fullKeyId] == true) {
haveValidSignature = true;
gotSignatureFromCache = true;
} else if (validSignatures![otherUserId][fullKeyId] == false) {
} else if (validSignatures?[otherUserId][fullKeyId] == false) {
haveValidSignature = false;
gotSignatureFromCache = true;
}
}
if (!gotSignatureFromCache && key.ed25519Key != null) {
// validate the signature manually
haveValidSignature = _verifySignature(key.ed25519Key!, signature);
validSignatures ??= <String, dynamic>{};
if (!validSignatures!.containsKey(otherUserId)) {
validSignatures![otherUserId] = <String, dynamic>{};
final validSignatures = this.validSignatures ??= <String, dynamic>{};
if (!validSignatures.containsKey(otherUserId)) {
validSignatures[otherUserId] = <String, dynamic>{};
}
validSignatures![otherUserId][fullKeyId] = haveValidSignature;
validSignatures[otherUserId][fullKeyId] = haveValidSignature;
}
if (!haveValidSignature) {
// no valid signature, this key is useless

View File

@ -22,7 +22,7 @@ dependencies:
js: ^0.6.3
slugify: ^2.0.0
html: ^0.15.0
collection: ^1.15.0-nullsafety.4
collection: ^1.15.0
dev_dependencies:
pedantic: ^1.11.0