fix: Properly handle initial device key uploading failures and better handle OTK upload failures

This commit is contained in:
Sorunome 2021-01-12 12:25:39 +01:00
parent 0a89ac5564
commit fddced2b3a
No known key found for this signature in database
GPG Key ID: B19471D07FC9BE9C
2 changed files with 62 additions and 22 deletions

View File

@ -56,12 +56,14 @@ class OlmManager {
await olm.init(); await olm.init();
_olmAccount = olm.Account(); _olmAccount = olm.Account();
_olmAccount.create(); _olmAccount.create();
if (await uploadKeys(uploadDeviceKeys: true) == false) { if (await uploadKeys(uploadDeviceKeys: true, updateDatabase: false) ==
false) {
throw ('Upload key failed'); throw ('Upload key failed');
} }
} catch (_) { } catch (_) {
_olmAccount?.free(); _olmAccount?.free();
_olmAccount = null; _olmAccount = null;
rethrow;
} }
} else { } else {
try { try {
@ -71,6 +73,7 @@ class OlmManager {
} catch (_) { } catch (_) {
_olmAccount?.free(); _olmAccount?.free();
_olmAccount = null; _olmAccount = null;
rethrow;
} }
} }
} }
@ -136,7 +139,9 @@ class OlmManager {
/// Generates new one time keys, signs everything and upload it to the server. /// Generates new one time keys, signs everything and upload it to the server.
Future<bool> uploadKeys( Future<bool> uploadKeys(
{bool uploadDeviceKeys = false, int oldKeyCount = 0}) async { {bool uploadDeviceKeys = false,
int oldKeyCount = 0,
bool updateDatabase = true}) async {
if (!enabled) { if (!enabled) {
return true; return true;
} }
@ -147,13 +152,20 @@ class OlmManager {
_uploadKeysLock = true; _uploadKeysLock = true;
try { try {
// 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'].entries.length;
// generate one-time keys // generate one-time keys
// we generate 2/3rds of max, so that other keys people may still have can // we generate 2/3rds of max, so that other keys people may still have can
// still be used // still be used
final oneTimeKeysCount = final oneTimeKeysCount =
(_olmAccount.max_number_of_one_time_keys() * 2 / 3).floor() - (_olmAccount.max_number_of_one_time_keys() * 2 / 3).floor() -
oldKeyCount; oldKeyCount -
_olmAccount.generate_one_time_keys(oneTimeKeysCount); oldOTKsNeedingUpload;
if (oneTimeKeysCount > 0) {
_olmAccount.generate_one_time_keys(oneTimeKeysCount);
}
final Map<String, dynamic> oneTimeKeys = final Map<String, dynamic> oneTimeKeys =
json.decode(_olmAccount.one_time_keys()); json.decode(_olmAccount.one_time_keys());
@ -194,14 +206,23 @@ class OlmManager {
signJson(keysContent['device_keys'] as Map<String, dynamic>); signJson(keysContent['device_keys'] as Map<String, dynamic>);
} }
// we save the generated OTKs into the database.
// in case the app gets killed during upload or the upload fails due to bad network
// we can still re-try later
if (updateDatabase) {
await client.database?.updateClientKeys(pickledOlmAccount, client.id);
}
final response = await client.uploadDeviceKeys( final response = await client.uploadDeviceKeys(
deviceKeys: uploadDeviceKeys deviceKeys: uploadDeviceKeys
? MatrixDeviceKeys.fromJson(keysContent['device_keys']) ? MatrixDeviceKeys.fromJson(keysContent['device_keys'])
: null, : null,
oneTimeKeys: signedOneTimeKeys, oneTimeKeys: signedOneTimeKeys,
); );
// mark the OTKs as published and save that to datbase
_olmAccount.mark_keys_as_published(); _olmAccount.mark_keys_as_published();
await client.database?.updateClientKeys(pickledOlmAccount, client.id); if (updateDatabase) {
await client.database?.updateClientKeys(pickledOlmAccount, client.id);
}
return response['signed_curve25519'] == oneTimeKeysCount; return response['signed_curve25519'] == oneTimeKeysCount;
} finally { } finally {
_uploadKeysLock = false; _uploadKeysLock = false;

View File

@ -22,6 +22,7 @@ import 'dart:core';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:olm/olm.dart' as olm;
import 'package:pedantic/pedantic.dart'; import 'package:pedantic/pedantic.dart';
import '../encryption.dart'; import '../encryption.dart';
@ -335,12 +336,17 @@ class Client extends MatrixApi {
response.userId == null) { response.userId == null) {
throw Exception('Registered but token, device ID or user ID is null.'); throw Exception('Registered but token, device ID or user ID is null.');
} }
await init( try {
newToken: response.accessToken, await init(
newUserID: response.userId, newToken: response.accessToken,
newHomeserver: homeserver, newUserID: response.userId,
newDeviceName: initialDeviceDisplayName ?? '', newHomeserver: homeserver,
newDeviceID: response.deviceId); newDeviceName: initialDeviceDisplayName ?? '',
newDeviceID: response.deviceId);
} catch (_) {
await logout().catchError((_) => null);
rethrow;
}
return response; return response;
} }
@ -380,14 +386,19 @@ class Client extends MatrixApi {
loginResp.userId == null) { loginResp.userId == null) {
throw Exception('Registered but token, device ID or user ID is null.'); throw Exception('Registered but token, device ID or user ID is null.');
} }
await init( try {
newToken: loginResp.accessToken, await init(
newUserID: loginResp.userId, newToken: loginResp.accessToken,
newHomeserver: homeserver, newUserID: loginResp.userId,
newDeviceName: initialDeviceDisplayName ?? '', newHomeserver: homeserver,
newDeviceID: loginResp.deviceId, newDeviceName: initialDeviceDisplayName ?? '',
); newDeviceID: loginResp.deviceId,
return loginResp; );
return loginResp;
} catch (_) {
await logout().catchError((_) => null);
rethrow;
}
} }
/// Sends a logout command to the homeserver and clears all local data, /// Sends a logout command to the homeserver and clears all local data,
@ -767,9 +778,17 @@ class Client extends MatrixApi {
_initLock = false; _initLock = false;
encryption?.dispose(); encryption?.dispose();
encryption = try {
Encryption(client: this, enableE2eeRecovery: enableE2eeRecovery); // make sure to throw an exception if libolm doesn't exist
await encryption.init(olmAccount); await olm.init();
olm.get_library_version();
encryption =
Encryption(client: this, enableE2eeRecovery: enableE2eeRecovery);
} catch (_) {
encryption?.dispose();
encryption = null;
}
await encryption?.init(olmAccount);
if (database != null) { if (database != null) {
if (id != null) { if (id != null) {