From 60956bde0019bbf0d7ef3181676fee9ca7c1584f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 11 Oct 2021 18:55:07 +0200 Subject: [PATCH] chore: port the remaining encryption files to nullsafety --- .gitignore | 1 + lib/encryption/cross_signing.dart | 60 +++++++++---------- lib/encryption/encryption.dart | 4 +- lib/encryption/key_manager.dart | 23 ++++--- lib/encryption/key_verification_manager.dart | 14 ++--- lib/encryption/olm_manager.dart | 28 +++++---- .../utils/json_signature_check_extension.dart | 1 - lib/encryption/utils/key_verification.dart | 2 +- .../utils/outbound_group_session.dart | 23 ++++--- .../utils/stored_inbound_group_session.dart | 19 +++--- lib/src/database/database_api.dart | 1 - lib/src/database/hive_database.dart | 5 +- lib/src/utils/device_keys_list.dart | 2 +- test/database_api_test.dart | 1 - test/encryption/cross_signing_test.dart | 2 +- test/encryption/key_manager_test.dart | 4 +- 16 files changed, 91 insertions(+), 99 deletions(-) diff --git a/.gitignore b/.gitignore index 0ab205ed..50171198 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.log *.pyc *.swp +*.swo .DS_Store .atom/ .buildlog/ diff --git a/lib/encryption/cross_signing.dart b/lib/encryption/cross_signing.dart index d45577bd..73637f5a 100644 --- a/lib/encryption/cross_signing.dart +++ b/lib/encryption/cross_signing.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020, 2021 Famedly GmbH @@ -35,7 +34,7 @@ class CrossSigning { final keyObj = olm.PkSigning(); try { return keyObj.init_with_seed(base64.decode(secret)) == - client.userDeviceKeys[client.userID].selfSigningKey.ed25519Key; + client.userDeviceKeys[client.userID]!.selfSigningKey!.ed25519Key; } catch (_) { return false; } finally { @@ -47,7 +46,7 @@ class CrossSigning { final keyObj = olm.PkSigning(); try { return keyObj.init_with_seed(base64.decode(secret)) == - client.userDeviceKeys[client.userID].userSigningKey.ed25519Key; + client.userDeviceKeys[client.userID]!.userSigningKey!.ed25519Key; } catch (_) { return false; } finally { @@ -73,10 +72,10 @@ class CrossSigning { } Future selfSign( - {String passphrase, - String recoveryKey, - String keyOrPassphrase, - OpenSSSS openSsss}) async { + {String? passphrase, + String? recoveryKey, + String? keyOrPassphrase, + OpenSSSS? openSsss}) async { var handle = openSsss; if (handle == null) { handle = encryption.ssss.open(EventTypes.CrossSigningMasterKey); @@ -97,13 +96,12 @@ class CrossSigning { } finally { keyObj.free(); } - if (masterPubkey == null || - !client.userDeviceKeys.containsKey(client.userID) || - !client.userDeviceKeys[client.userID].deviceKeys - .containsKey(client.deviceID)) { + final userDeviceKeys = + client.userDeviceKeys[client.userID]?.deviceKeys[client.deviceID]; + if (masterPubkey == null || userDeviceKeys == null) { throw Exception('Master or user keys not found'); } - final masterKey = client.userDeviceKeys[client.userID].masterKey; + final masterKey = client.userDeviceKeys[client.userID]?.masterKey; if (masterKey == null || masterKey.ed25519Key != masterPubkey) { throw Exception('Master pubkey key doesn\'t match'); } @@ -112,7 +110,7 @@ class CrossSigning { // and now sign both our own key and our master key await sign([ masterKey, - client.userDeviceKeys[client.userID].deviceKeys[client.deviceID] + userDeviceKeys, ]); } @@ -123,20 +121,26 @@ class CrossSigning { key.identifier != client.deviceID); Future sign(List keys) async { - Uint8List selfSigningKey; - Uint8List userSigningKey; final signedKeys = []; + Uint8List? selfSigningKey; + Uint8List? userSigningKey; + final userKeys = client.userDeviceKeys[client.userID]; + if (userKeys == null) { + throw Exception('[sign] keys are not in cache but sign was called'); + } + final addSignature = (SignableKey key, SignableKey signedWith, String signature) { if (key == null || signedWith == null || signature == null) { return; } final signedKey = key.cloneForSigning(); - signedKey.signatures[signedWith.userId] = {}; - signedKey.signatures[signedWith.userId] - ['ed25519:${signedWith.identifier}'] = signature; + ((signedKey.signatures ??= + >{})[signedWith.userId] ??= + {})['ed25519:${signedWith.identifier}'] = signature; signedKeys.add(signedKey); }; + for (final key in keys) { if (key.userId == client.userID) { // we are singing a key of ourself @@ -145,11 +149,7 @@ class CrossSigning { // okay, we'll sign our own master key final signature = encryption.olmManager.signString(key.signingContent); - addSignature( - key, - client - .userDeviceKeys[client.userID].deviceKeys[client.deviceID], - signature); + addSignature(key, userKeys.deviceKeys[client.deviceID]!, signature); } // we don't care about signing other cross-signing keys } else { @@ -159,8 +159,7 @@ class CrossSigning { ''); if (selfSigningKey.isNotEmpty) { final signature = _sign(key.signingContent, selfSigningKey); - addSignature(key, - client.userDeviceKeys[client.userID].selfSigningKey, signature); + addSignature(key, userKeys.selfSigningKey!, signature); } } } else if (key is CrossSigningKey && key.usage.contains('master')) { @@ -170,8 +169,7 @@ class CrossSigning { ''); if (userSigningKey.isNotEmpty) { final signature = _sign(key.signingContent, userSigningKey); - addSignature(key, client.userDeviceKeys[client.userID].userSigningKey, - signature); + addSignature(key, userKeys.userSigningKey!, signature); } } } @@ -181,19 +179,19 @@ class CrossSigning { for (final key in signedKeys) { if (key.identifier == null || key.signatures == null || - key.signatures.isEmpty) { + key.signatures?.isEmpty != false) { continue; } if (!payload.containsKey(key.userId)) { payload[key.userId] = >{}; } - if (payload[key.userId].containsKey(key.identifier)) { + if (payload[key.userId]?[key.identifier]?['signatures'] != null) { // we need to merge signature objects - payload[key.userId][key.identifier]['signatures'] + payload[key.userId]![key.identifier]!['signatures'] .addAll(key.signatures); } else { // we can just add signatures - payload[key.userId][key.identifier] = key.toJson(); + payload[key.userId]![key.identifier!] = key.toJson(); } } diff --git a/lib/encryption/encryption.dart b/lib/encryption/encryption.dart index 95b1242e..b2a2c761 100644 --- a/lib/encryption/encryption.dart +++ b/lib/encryption/encryption.dart @@ -355,10 +355,10 @@ class Encryption { final encryptedPayload = { 'algorithm': AlgorithmTypes.megolmV1AesSha2, 'ciphertext': - sess!.outboundGroupSession.encrypt(json.encode(payloadContent)), + sess!.outboundGroupSession!.encrypt(json.encode(payloadContent)), 'device_id': client.deviceID, 'sender_key': identityKey, - 'session_id': sess.outboundGroupSession.session_id(), + 'session_id': sess.outboundGroupSession!.session_id(), if (mRelatesTo != null) 'm.relates_to': mRelatesTo, }; await keyManager.storeOutboundGroupSession(roomId, sess); diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index 3a5a69e8..eaf16ac6 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -281,7 +281,7 @@ class KeyManager { {bool wipe = false, bool use = true}) async { final room = client.getRoomById(roomId); final sess = getOutboundGroupSession(roomId); - if (room == null || sess == null) { + if (room == null || sess == null || sess.outboundGroupSession == null) { return true; } @@ -292,7 +292,7 @@ class KeyManager { final maxMessages = encryptionContent?.rotationPeriodMsgs ?? 100; final maxAge = encryptionContent?.rotationPeriodMs ?? 604800000; // default of one week - if (sess.sentMessages >= maxMessages || + if ((sess.sentMessages ?? maxMessages) >= maxMessages || sess.creationTime .add(Duration(milliseconds: maxAge)) .isBefore(DateTime.now())) { @@ -301,7 +301,7 @@ class KeyManager { } final inboundSess = await loadInboundGroupSession(room.id, - sess.outboundGroupSession.session_id(), encryption.identityKey!); + sess.outboundGroupSession!.session_id(), encryption.identityKey!); if (inboundSess == null) { wipe = true; } @@ -371,13 +371,12 @@ class KeyManager { return false; } // okay, we use the outbound group session! - sess.sentMessages++; sess.devices = newDeviceKeyIds; final rawSession = { 'algorithm': AlgorithmTypes.megolmV1AesSha2, 'room_id': room.id, - 'session_id': sess.outboundGroupSession.session_id(), - 'session_key': sess.outboundGroupSession.session_key(), + 'session_id': sess.outboundGroupSession!.session_id(), + 'session_key': sess.outboundGroupSession!.session_key(), }; try { devicesToReceive.removeWhere((k) => !k.encryptToDevice); @@ -389,17 +388,17 @@ class KeyManager { .containsKey(device.curve25519Key) || inboundSess.allowedAtIndex[device.userId]![ device.curve25519Key]! > - sess.outboundGroupSession.message_index()) { + sess.outboundGroupSession!.message_index()) { inboundSess .allowedAtIndex[device.userId]![device.curve25519Key!] = - sess.outboundGroupSession.message_index(); + sess.outboundGroupSession!.message_index(); } } if (client.database != null) { await client.database.updateInboundGroupSessionAllowedAtIndex( json.encode(inboundSess!.allowedAtIndex), room.id, - sess.outboundGroupSession.session_id()); + sess.outboundGroupSession!.session_id()); } // send out the key await client.sendToDeviceEncryptedChunked( @@ -425,10 +424,9 @@ class KeyManager { String roomId, OutboundGroupSession sess) async { await client.database?.storeOutboundGroupSession( roomId, - sess.outboundGroupSession.pickle(client.userID), + sess.outboundGroupSession!.pickle(client.userID), json.encode(sess.devices), - sess.creationTime.millisecondsSinceEpoch, - sess.sentMessages); + sess.creationTime.millisecondsSinceEpoch); } final Map> @@ -500,7 +498,6 @@ class KeyManager { devices: deviceKeyIds, creationTime: DateTime.now(), outboundGroupSession: outboundGroupSession, - sentMessages: 0, key: client.userID, ); try { diff --git a/lib/encryption/key_verification_manager.dart b/lib/encryption/key_verification_manager.dart index 9185f436..2b1dbfb2 100644 --- a/lib/encryption/key_verification_manager.dart +++ b/lib/encryption/key_verification_manager.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020, 2021 Famedly GmbH @@ -50,10 +49,10 @@ class KeyVerificationManager { if (request.transactionId == null) { return; } - _requests[request.transactionId] = request; + _requests[request.transactionId!] = request; } - KeyVerification getRequest(String requestId) => _requests[requestId]; + KeyVerification? getRequest(String requestId) => _requests[requestId]; Future handleToDeviceEvent(ToDeviceEvent event) async { if (!event.type.startsWith('m.key.verification.') || @@ -65,10 +64,11 @@ class KeyVerificationManager { if (transactionId == null) { return; // TODO: send cancel with unknown transaction id } - if (_requests.containsKey(transactionId)) { + final request = _requests[transactionId]; + if (request != null) { // make sure that new requests can't come from ourself if (!{EventTypes.KeyVerificationRequest}.contains(event.type)) { - await _requests[transactionId].handlePayload(event.type, event.content); + await request.handlePayload(event.type, event.content); } } else { if (!{EventTypes.KeyVerificationRequest, EventTypes.KeyVerificationStart} @@ -105,8 +105,8 @@ class KeyVerificationManager { final transactionId = KeyVerification.getTransactionId(event['content']) ?? event['event_id']; - if (_requests.containsKey(transactionId)) { - final req = _requests[transactionId]; + final req = _requests[transactionId]; + if (req != null) { final otherDeviceId = event['content']['from_device']; if (event['sender'] != client.userID) { await req.handlePayload(type, event['content'], event['event_id']); diff --git a/lib/encryption/olm_manager.dart b/lib/encryption/olm_manager.dart index fbfc40fc..6573be08 100644 --- a/lib/encryption/olm_manager.dart +++ b/lib/encryption/olm_manager.dart @@ -145,7 +145,8 @@ class OlmManager { bool updateDatabase = true, bool? unusedFallbackKey = false, }) async { - if (!enabled) { + final _olmAccount = this._olmAccount; + if (_olmAccount == null) { return true; } @@ -161,22 +162,22 @@ class OlmManager { // check if we have OTKs that still need uploading. If we do, we don't try to generate new ones, // instead we try to upload the old ones first final oldOTKsNeedingUpload = json - .decode(_olmAccount!.one_time_keys())['curve25519'] + .decode(_olmAccount.one_time_keys())['curve25519'] .entries .length as int; // generate one-time keys // we generate 2/3rds of max, so that other keys people may still have can // still be used final oneTimeKeysCount = - (_olmAccount!.max_number_of_one_time_keys() * 2 / 3).floor() - + (_olmAccount.max_number_of_one_time_keys() * 2 / 3).floor() - oldKeyCount - oldOTKsNeedingUpload; if (oneTimeKeysCount > 0) { - _olmAccount!.generate_one_time_keys(oneTimeKeysCount); + _olmAccount.generate_one_time_keys(oneTimeKeysCount); } uploadedOneTimeKeysCount = oneTimeKeysCount + oldOTKsNeedingUpload; final Map oneTimeKeys = - json.decode(_olmAccount!.one_time_keys()); + json.decode(_olmAccount.one_time_keys()); // now sign all the one-time keys for (final entry in oneTimeKeys['curve25519'].entries) { @@ -191,8 +192,8 @@ class OlmManager { final signedFallbackKeys = {}; if (encryption.isMinOlmVersion(3, 2, 0) && unusedFallbackKey == false) { // we don't have an unused fallback key uploaded....so let's change that! - _olmAccount!.generate_fallback_key(); - final fallbackKey = json.decode(_olmAccount!.fallback_key()); + _olmAccount.generate_fallback_key(); + final fallbackKey = json.decode(_olmAccount.fallback_key()); // now sign all the fallback keys for (final entry in fallbackKey['curve25519'].entries) { final key = entry.key; @@ -219,7 +220,7 @@ class OlmManager { }; if (uploadDeviceKeys) { final Map keys = - json.decode(_olmAccount!.identity_keys()); + json.decode(_olmAccount.identity_keys()); for (final entry in keys.entries) { final algorithm = entry.key; final value = entry.value; @@ -244,7 +245,7 @@ class OlmManager { fallbackKeys: signedFallbackKeys, ); // mark the OTKs as published and save that to datbase - _olmAccount!.mark_keys_as_published(); + _olmAccount.mark_keys_as_published(); if (updateDatabase) { await client.database?.updateClientKeys(pickledOlmAccount!); } @@ -553,14 +554,16 @@ class OlmManager { client.userDeviceKeys[userId]!.deviceKeys[deviceId]!.curve25519Key; for (final Map deviceKey in deviceKeysEntry.value.values) { - if (!deviceKey.checkJsonSignature(fingerprintKey, userId, deviceId)) { + if (fingerprintKey == null || + identityKey == null || + !deviceKey.checkJsonSignature(fingerprintKey, userId, deviceId)) { continue; } Logs().v('[OlmManager] Starting session with $userId:$deviceId'); final session = olm.Session(); try { session.create_outbound( - _olmAccount!, identityKey!, deviceKey['key']); + _olmAccount!, identityKey, deviceKey['key']); await storeOlmSession(OlmSession( key: client.userID, identityKey: identityKey, @@ -666,8 +669,7 @@ class OlmManager { '[OlmManager] Device ${device.userId}:${device.deviceId} generated a new olm session, replaying last sent message...'); final lastSentMessageRes = await client.database .getLastSentMessageUserDeviceKey(device.userId, device.deviceId!); - if (lastSentMessageRes.isEmpty || - (lastSentMessageRes.first?.isEmpty ?? true)) { + if (lastSentMessageRes.isEmpty || (lastSentMessageRes.first.isEmpty)) { return; } final lastSentMessage = json.decode(lastSentMessageRes.first); diff --git a/lib/encryption/utils/json_signature_check_extension.dart b/lib/encryption/utils/json_signature_check_extension.dart index b2505d5d..7c89bca8 100644 --- a/lib/encryption/utils/json_signature_check_extension.dart +++ b/lib/encryption/utils/json_signature_check_extension.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020, 2021 Famedly GmbH diff --git a/lib/encryption/utils/key_verification.dart b/lib/encryption/utils/key_verification.dart index 27c6b931..449568ac 100644 --- a/lib/encryption/utils/key_verification.dart +++ b/lib/encryption/utils/key_verification.dart @@ -143,7 +143,7 @@ class KeyVerification { method?.dispose(); } - static String getTransactionId(Map payload) { + static String? getTransactionId(Map payload) { return payload['transaction_id'] ?? (payload['m.relates_to'] is Map ? payload['m.relates_to']['event_id'] diff --git a/lib/encryption/utils/outbound_group_session.dart b/lib/encryption/utils/outbound_group_session.dart index 7328577c..45d9a090 100644 --- a/lib/encryption/utils/outbound_group_session.dart +++ b/lib/encryption/utils/outbound_group_session.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020, 2021 Famedly GmbH @@ -28,24 +27,23 @@ class OutboundGroupSession { /// This way we can easily know if a new user is added, leaves, a new devices is added, and, /// very importantly, if we block a device. These are all important for determining if/when /// an outbound session needs to be rotated. - Map> devices; - DateTime creationTime; - olm.OutboundGroupSession outboundGroupSession; - int sentMessages; + Map> devices = {}; + // Default to a date, that would get this session rotated in any case to make handling easier + DateTime creationTime = DateTime.fromMillisecondsSinceEpoch(0); + olm.OutboundGroupSession? outboundGroupSession; + int? get sentMessages => outboundGroupSession?.message_index(); bool get isValid => outboundGroupSession != null; final String key; OutboundGroupSession( - {this.devices, - this.creationTime, - this.outboundGroupSession, - this.sentMessages, - this.key}); + {required this.devices, + required this.creationTime, + required this.outboundGroupSession, + required this.key}); OutboundGroupSession.fromJson(Map dbEntry, String key) : key = key { try { - devices = {}; for (final entry in json.decode(dbEntry['device_ids']).entries) { devices[entry.key] = Map.from(entry.value); } @@ -58,10 +56,9 @@ class OutboundGroupSession { } outboundGroupSession = olm.OutboundGroupSession(); try { - outboundGroupSession.unpickle(key, dbEntry['pickle']); + outboundGroupSession!.unpickle(key, dbEntry['pickle']); creationTime = DateTime.fromMillisecondsSinceEpoch(dbEntry['creation_time']); - sentMessages = dbEntry['sent_messages']; } catch (e, s) { dispose(); Logs().e('[LibOlm] Unable to unpickle outboundGroupSession', e, s); diff --git a/lib/encryption/utils/stored_inbound_group_session.dart b/lib/encryption/utils/stored_inbound_group_session.dart index 534f4564..028ec404 100644 --- a/lib/encryption/utils/stored_inbound_group_session.dart +++ b/lib/encryption/utils/stored_inbound_group_session.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2021 Famedly GmbH @@ -29,15 +28,15 @@ class StoredInboundGroupSession { final String senderClaimedKeys; StoredInboundGroupSession({ - this.roomId, - this.sessionId, - this.pickle, - this.content, - this.indexes, - this.allowedAtIndex, - this.uploaded, - this.senderKey, - this.senderClaimedKeys, + required this.roomId, + required this.sessionId, + required this.pickle, + required this.content, + required this.indexes, + required this.allowedAtIndex, + required this.uploaded, + required this.senderKey, + required this.senderClaimedKeys, }); factory StoredInboundGroupSession.fromJson(Map json) => diff --git a/lib/src/database/database_api.dart b/lib/src/database/database_api.dart index f96f3c37..9345d8f2 100644 --- a/lib/src/database/database_api.dart +++ b/lib/src/database/database_api.dart @@ -146,7 +146,6 @@ abstract class DatabaseApi { String pickle, String deviceIds, int creationTime, - int sentMessages, ); Future updateClientKeys( diff --git a/lib/src/database/hive_database.dart b/lib/src/database/hive_database.dart index a2ca7542..0a7a5cab 100644 --- a/lib/src/database/hive_database.dart +++ b/lib/src/database/hive_database.dart @@ -1028,14 +1028,13 @@ class FamedlySdkHiveDatabase extends DatabaseApi { } @override - Future storeOutboundGroupSession(String roomId, String pickle, - String deviceIds, int creationTime, int sentMessages) async { + Future storeOutboundGroupSession( + String roomId, String pickle, String deviceIds, int creationTime) async { await _outboundGroupSessionsBox.put(roomId.toHiveKey, { 'room_id': roomId, 'pickle': pickle, 'device_ids': deviceIds, 'creation_time': creationTime, - 'sent_messages': sentMessages, }); return; } diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index a264e51e..1c76b750 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -299,7 +299,7 @@ abstract class SignableKey extends MatrixSignableKey { // or else we just recurse into that key and chack if it works out final haveChain = key.hasValidSignatureChain( verifiedOnly: verifiedOnly, - visited: visited, + visited: visited_, onlyValidateUserIds: onlyValidateUserIds); if (haveChain) { return true; diff --git a/test/database_api_test.dart b/test/database_api_test.dart index fe18752a..ca405984 100644 --- a/test/database_api_test.dart +++ b/test/database_api_test.dart @@ -342,7 +342,6 @@ void testDatabase( 'pickle', '{}', 0, - 0, ); final session = await database.getOutboundGroupSession( '!testroom:example.com', diff --git a/test/encryption/cross_signing_test.dart b/test/encryption/cross_signing_test.dart index 2288c091..56628249 100644 --- a/test/encryption/cross_signing_test.dart +++ b/test/encryption/cross_signing_test.dart @@ -99,7 +99,7 @@ void main() { ]); final body = json.decode(FakeMatrixApi .calledEndpoints['/client/r0/keys/signatures/upload'].first); - expect(body['@test:fakeServer.notExisting'].containsKey('OTHERDEVICE'), + expect(body['@test:fakeServer.notExisting']?.containsKey('OTHERDEVICE'), true); expect( body['@test:fakeServer.notExisting'].containsKey( diff --git a/test/encryption/key_manager_test.dart b/test/encryption/key_manager_test.dart index 7a896a4c..0b93386c 100644 --- a/test/encryption/key_manager_test.dart +++ b/test/encryption/key_manager_test.dart @@ -124,7 +124,9 @@ void main() { 0); // rotate after too many messages - sess.sentMessages = 300; + Iterable.generate(300).forEach((_) { + sess.outboundGroupSession.encrypt('some string'); + }); await client.encryption.keyManager.clearOrUseOutboundGroupSession(roomId); expect( client.encryption.keyManager.getOutboundGroupSession(roomId) != null,