chore: port the remaining encryption files to nullsafety

This commit is contained in:
Nicolas Werner 2021-10-11 18:55:07 +02:00
parent 3ae42d1a88
commit 60956bde00
16 changed files with 91 additions and 99 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
*.log
*.pyc
*.swp
*.swo
.DS_Store
.atom/
.buildlog/

View File

@ -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<void> 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<void> sign(List<SignableKey> keys) async {
Uint8List selfSigningKey;
Uint8List userSigningKey;
final signedKeys = <MatrixSignableKey>[];
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] = <String, String>{};
signedKey.signatures[signedWith.userId]
['ed25519:${signedWith.identifier}'] = signature;
((signedKey.signatures ??=
<String, Map<String, String>>{})[signedWith.userId] ??=
<String, String>{})['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] = <String, Map<String, dynamic>>{};
}
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();
}
}

View File

@ -355,10 +355,10 @@ class Encryption {
final encryptedPayload = <String, dynamic>{
'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);

View File

@ -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 = <String, dynamic>{
'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<String, Future<OutboundGroupSession>>
@ -500,7 +498,6 @@ class KeyManager {
devices: deviceKeyIds,
creationTime: DateTime.now(),
outboundGroupSession: outboundGroupSession,
sentMessages: 0,
key: client.userID,
);
try {

View File

@ -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<void> 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']);

View File

@ -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<String, dynamic> 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 = <String, dynamic>{};
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<String, dynamic> 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<String, dynamic> 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);

View File

@ -1,4 +1,3 @@
// @dart=2.9
/*
* Famedly Matrix SDK
* Copyright (C) 2020, 2021 Famedly GmbH

View File

@ -143,7 +143,7 @@ class KeyVerification {
method?.dispose();
}
static String getTransactionId(Map<String, dynamic> payload) {
static String? getTransactionId(Map<String, dynamic> payload) {
return payload['transaction_id'] ??
(payload['m.relates_to'] is Map
? payload['m.relates_to']['event_id']

View File

@ -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<String, Map<String, bool>> devices;
DateTime creationTime;
olm.OutboundGroupSession outboundGroupSession;
int sentMessages;
Map<String, Map<String, bool>> 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<String, dynamic> dbEntry, String key)
: key = key {
try {
devices = {};
for (final entry in json.decode(dbEntry['device_ids']).entries) {
devices[entry.key] = Map<String, bool>.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);

View File

@ -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<String, dynamic> json) =>

View File

@ -146,7 +146,6 @@ abstract class DatabaseApi {
String pickle,
String deviceIds,
int creationTime,
int sentMessages,
);
Future updateClientKeys(

View File

@ -1028,14 +1028,13 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
}
@override
Future<void> storeOutboundGroupSession(String roomId, String pickle,
String deviceIds, int creationTime, int sentMessages) async {
Future<void> storeOutboundGroupSession(
String roomId, String pickle, String deviceIds, int creationTime) async {
await _outboundGroupSessionsBox.put(roomId.toHiveKey, <String, dynamic>{
'room_id': roomId,
'pickle': pickle,
'device_ids': deviceIds,
'creation_time': creationTime,
'sent_messages': sentMessages,
});
return;
}

View File

@ -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;

View File

@ -342,7 +342,6 @@ void testDatabase(
'pickle',
'{}',
0,
0,
);
final session = await database.getOutboundGroupSession(
'!testroom:example.com',

View File

@ -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(

View File

@ -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,