LCOV - code coverage report
Current view: top level - lib/encryption/utils - bootstrap.dart (source / functions) Coverage Total Hit
Test: merged.info Lines: 81.6 % 283 231
Test Date: 2025-01-14 13:39:53 Functions: - 0 0

            Line data    Source code
       1              : /*
       2              :  *   Famedly Matrix SDK
       3              :  *   Copyright (C) 2020, 2021 Famedly GmbH
       4              :  *
       5              :  *   This program is free software: you can redistribute it and/or modify
       6              :  *   it under the terms of the GNU Affero General Public License as
       7              :  *   published by the Free Software Foundation, either version 3 of the
       8              :  *   License, or (at your option) any later version.
       9              :  *
      10              :  *   This program is distributed in the hope that it will be useful,
      11              :  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
      12              :  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      13              :  *   GNU Affero General Public License for more details.
      14              :  *
      15              :  *   You should have received a copy of the GNU Affero General Public License
      16              :  *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
      17              :  */
      18              : 
      19              : import 'dart:convert';
      20              : import 'dart:typed_data';
      21              : 
      22              : import 'package:canonical_json/canonical_json.dart';
      23              : import 'package:olm/olm.dart' as olm;
      24              : 
      25              : import 'package:matrix/encryption/encryption.dart';
      26              : import 'package:matrix/encryption/key_manager.dart';
      27              : import 'package:matrix/encryption/ssss.dart';
      28              : import 'package:matrix/encryption/utils/base64_unpadded.dart';
      29              : import 'package:matrix/matrix.dart';
      30              : 
      31              : enum BootstrapState {
      32              :   /// Is loading.
      33              :   loading,
      34              : 
      35              :   /// Existing SSSS found, should we wipe it?
      36              :   askWipeSsss,
      37              : 
      38              :   /// Ask if an existing SSSS should be userDeviceKeys
      39              :   askUseExistingSsss,
      40              : 
      41              :   /// Ask to unlock all the SSSS keys
      42              :   askUnlockSsss,
      43              : 
      44              :   /// SSSS is in a bad state, continue with potential dataloss?
      45              :   askBadSsss,
      46              : 
      47              :   /// Ask for new SSSS key / passphrase
      48              :   askNewSsss,
      49              : 
      50              :   /// Open an existing SSSS key
      51              :   openExistingSsss,
      52              : 
      53              :   /// Ask if cross signing should be wiped
      54              :   askWipeCrossSigning,
      55              : 
      56              :   /// Ask if cross signing should be set up
      57              :   askSetupCrossSigning,
      58              : 
      59              :   /// Ask if online key backup should be wiped
      60              :   askWipeOnlineKeyBackup,
      61              : 
      62              :   /// Ask if the online key backup should be set up
      63              :   askSetupOnlineKeyBackup,
      64              : 
      65              :   /// An error has been occured.
      66              :   error,
      67              : 
      68              :   /// done
      69              :   done,
      70              : }
      71              : 
      72              : /// Bootstrapping SSSS and cross-signing
      73              : class Bootstrap {
      74              :   final Encryption encryption;
      75            3 :   Client get client => encryption.client;
      76              :   void Function(Bootstrap)? onUpdate;
      77            2 :   BootstrapState get state => _state;
      78              :   BootstrapState _state = BootstrapState.loading;
      79              :   Map<String, OpenSSSS>? oldSsssKeys;
      80              :   OpenSSSS? newSsssKey;
      81              :   Map<String, String>? secretMap;
      82              : 
      83            1 :   Bootstrap({required this.encryption, this.onUpdate}) {
      84            2 :     if (analyzeSecrets().isNotEmpty) {
      85            1 :       state = BootstrapState.askWipeSsss;
      86              :     } else {
      87            1 :       state = BootstrapState.askNewSsss;
      88              :     }
      89              :   }
      90              : 
      91              :   // cache the secret analyzing so that we don't drop stuff a different client sets during bootstrapping
      92              :   Map<String, Set<String>>? _secretsCache;
      93              : 
      94              :   /// returns ssss from accountdata, eg: m.megolm_backup.v1, or your m.cross_signing stuff
      95            1 :   Map<String, Set<String>> analyzeSecrets() {
      96            1 :     final secretsCache = _secretsCache;
      97              :     if (secretsCache != null) {
      98              :       // deep-copy so that we can do modifications
      99            1 :       final newSecrets = <String, Set<String>>{};
     100            2 :       for (final s in secretsCache.entries) {
     101            4 :         newSecrets[s.key] = Set<String>.from(s.value);
     102              :       }
     103              :       return newSecrets;
     104              :     }
     105            1 :     final secrets = <String, Set<String>>{};
     106            4 :     for (final entry in client.accountData.entries) {
     107            1 :       final type = entry.key;
     108            1 :       final event = entry.value;
     109              :       final encryptedContent =
     110            2 :           event.content.tryGetMap<String, Object?>('encrypted');
     111              :       if (encryptedContent == null) {
     112              :         continue;
     113              :       }
     114              :       final validKeys = <String>{};
     115              :       final invalidKeys = <String>{};
     116            2 :       for (final keyEntry in encryptedContent.entries) {
     117            1 :         final key = keyEntry.key;
     118            1 :         final value = keyEntry.value;
     119            1 :         if (value is! Map) {
     120              :           // we don't add the key to invalidKeys as this was not a proper secret anyways!
     121              :           continue;
     122              :         }
     123            2 :         if (value['iv'] is! String ||
     124            2 :             value['ciphertext'] is! String ||
     125            2 :             value['mac'] is! String) {
     126            0 :           invalidKeys.add(key);
     127              :           continue;
     128              :         }
     129            3 :         if (!encryption.ssss.isKeyValid(key)) {
     130            1 :           invalidKeys.add(key);
     131              :           continue;
     132              :         }
     133            1 :         validKeys.add(key);
     134              :       }
     135            2 :       if (validKeys.isEmpty && invalidKeys.isEmpty) {
     136              :         continue; // this didn't contain any keys anyways!
     137              :       }
     138              :       // if there are no valid keys and only invalid keys then the validKeys set will be empty
     139              :       // from that we know that there were errors with this secret and that we won't be able to migrate it
     140            1 :       secrets[type] = validKeys;
     141              :     }
     142            1 :     _secretsCache = secrets;
     143            1 :     return analyzeSecrets();
     144              :   }
     145              : 
     146            1 :   Set<String> badSecrets() {
     147            1 :     final secrets = analyzeSecrets();
     148            3 :     secrets.removeWhere((k, v) => v.isNotEmpty);
     149            2 :     return Set<String>.from(secrets.keys);
     150              :   }
     151              : 
     152            1 :   String mostUsedKey(Map<String, Set<String>> secrets) {
     153            1 :     final usage = <String, int>{};
     154            2 :     for (final keys in secrets.values) {
     155            2 :       for (final key in keys) {
     156            2 :         usage.update(key, (i) => i + 1, ifAbsent: () => 1);
     157              :       }
     158              :     }
     159            2 :     final entriesList = usage.entries.toList();
     160            1 :     entriesList.sort((a, b) => a.value.compareTo(b.value));
     161            2 :     return entriesList.first.key;
     162              :   }
     163              : 
     164            1 :   Set<String> allNeededKeys() {
     165            1 :     final secrets = analyzeSecrets();
     166            1 :     secrets.removeWhere(
     167            2 :       (k, v) => v.isEmpty,
     168              :     ); // we don't care about the failed secrets here
     169              :     final keys = <String>{};
     170            3 :     final defaultKeyId = encryption.ssss.defaultKeyId;
     171            1 :     int removeKey(String key) {
     172            1 :       final sizeBefore = secrets.length;
     173            3 :       secrets.removeWhere((k, v) => v.contains(key));
     174            2 :       return sizeBefore - secrets.length;
     175              :     }
     176              : 
     177              :     // first we want to try the default key id
     178              :     if (defaultKeyId != null) {
     179            2 :       if (removeKey(defaultKeyId) > 0) {
     180            1 :         keys.add(defaultKeyId);
     181              :       }
     182              :     }
     183              :     // now we re-try as long as we have keys for all secrets
     184            1 :     while (secrets.isNotEmpty) {
     185            1 :       final key = mostUsedKey(secrets);
     186            1 :       removeKey(key);
     187            1 :       keys.add(key);
     188              :     }
     189              :     return keys;
     190              :   }
     191              : 
     192            1 :   void wipeSsss(bool wipe) {
     193            2 :     if (state != BootstrapState.askWipeSsss) {
     194            0 :       throw BootstrapBadStateException('Wrong State');
     195              :     }
     196              :     if (wipe) {
     197            1 :       state = BootstrapState.askNewSsss;
     198            3 :     } else if (encryption.ssss.defaultKeyId != null &&
     199            6 :         encryption.ssss.isKeyValid(encryption.ssss.defaultKeyId!)) {
     200            1 :       state = BootstrapState.askUseExistingSsss;
     201            2 :     } else if (badSecrets().isNotEmpty) {
     202            1 :       state = BootstrapState.askBadSsss;
     203              :     } else {
     204            0 :       migrateOldSsss();
     205              :     }
     206              :   }
     207              : 
     208            1 :   void useExistingSsss(bool use) {
     209            2 :     if (state != BootstrapState.askUseExistingSsss) {
     210            0 :       throw BootstrapBadStateException('Wrong State');
     211              :     }
     212              :     if (use) {
     213              :       try {
     214            0 :         newSsssKey = encryption.ssss.open(encryption.ssss.defaultKeyId);
     215            0 :         state = BootstrapState.openExistingSsss;
     216              :       } catch (e, s) {
     217            0 :         Logs().e('[Bootstrapping] Error open SSSS', e, s);
     218            0 :         state = BootstrapState.error;
     219              :         return;
     220              :       }
     221            2 :     } else if (badSecrets().isNotEmpty) {
     222            0 :       state = BootstrapState.askBadSsss;
     223              :     } else {
     224            1 :       migrateOldSsss();
     225              :     }
     226              :   }
     227              : 
     228            1 :   void ignoreBadSecrets(bool ignore) {
     229            2 :     if (state != BootstrapState.askBadSsss) {
     230            0 :       throw BootstrapBadStateException('Wrong State');
     231              :     }
     232              :     if (ignore) {
     233            0 :       migrateOldSsss();
     234              :     } else {
     235              :       // that's it, folks. We can't do anything here
     236            1 :       state = BootstrapState.error;
     237              :     }
     238              :   }
     239              : 
     240            1 :   void migrateOldSsss() {
     241            1 :     final keys = allNeededKeys();
     242            2 :     final oldSsssKeys = this.oldSsssKeys = {};
     243              :     try {
     244            2 :       for (final key in keys) {
     245            4 :         oldSsssKeys[key] = encryption.ssss.open(key);
     246              :       }
     247              :     } catch (e, s) {
     248            0 :       Logs().e('[Bootstrapping] Error construction ssss key', e, s);
     249            0 :       state = BootstrapState.error;
     250              :       return;
     251              :     }
     252            1 :     state = BootstrapState.askUnlockSsss;
     253              :   }
     254              : 
     255            1 :   void unlockedSsss() {
     256            2 :     if (state != BootstrapState.askUnlockSsss) {
     257            0 :       throw BootstrapBadStateException('Wrong State');
     258              :     }
     259            1 :     state = BootstrapState.askNewSsss;
     260              :   }
     261              : 
     262            1 :   Future<void> newSsss([String? passphrase]) async {
     263            2 :     if (state != BootstrapState.askNewSsss) {
     264            0 :       throw BootstrapBadStateException('Wrong State');
     265              :     }
     266            1 :     state = BootstrapState.loading;
     267              :     try {
     268            2 :       Logs().v('Create key...');
     269            4 :       newSsssKey = await encryption.ssss.createKey(passphrase);
     270            1 :       if (oldSsssKeys != null) {
     271              :         // alright, we have to re-encrypt old secrets with the new key
     272            1 :         final secrets = analyzeSecrets();
     273            1 :         Set<String> removeKey(String key) {
     274            1 :           final s = secrets.entries
     275            4 :               .where((e) => e.value.contains(key))
     276            3 :               .map((e) => e.key)
     277            1 :               .toSet();
     278            3 :           secrets.removeWhere((k, v) => v.contains(key));
     279              :           return s;
     280              :         }
     281              : 
     282            2 :         secretMap = <String, String>{};
     283            3 :         for (final entry in oldSsssKeys!.entries) {
     284            1 :           final key = entry.value;
     285            1 :           final keyId = entry.key;
     286            1 :           if (!key.isUnlocked) {
     287              :             continue;
     288              :           }
     289            2 :           for (final s in removeKey(keyId)) {
     290            3 :             Logs().v('Get stored key of type $s...');
     291            3 :             secretMap![s] = await key.getStored(s);
     292            2 :             Logs().v('Store new secret with this key...');
     293            4 :             await newSsssKey!.store(s, secretMap![s]!, add: true);
     294              :           }
     295              :         }
     296              :         // alright, we re-encrypted all the secrets. We delete the dead weight only *after* we set our key to the default key
     297              :       }
     298            5 :       await encryption.ssss.setDefaultKeyId(newSsssKey!.keyId);
     299            6 :       while (encryption.ssss.defaultKeyId != newSsssKey!.keyId) {
     300            0 :         Logs().v(
     301              :           'Waiting accountData to have the correct m.secret_storage.default_key',
     302              :         );
     303            0 :         await client.oneShotSync();
     304              :       }
     305            1 :       if (oldSsssKeys != null) {
     306            3 :         for (final entry in secretMap!.entries) {
     307            4 :           Logs().v('Validate and stripe other keys ${entry.key}...');
     308            4 :           await newSsssKey!.validateAndStripOtherKeys(entry.key, entry.value);
     309              :         }
     310            2 :         Logs().v('And make super sure we have everything cached...');
     311            2 :         await newSsssKey!.maybeCacheAll();
     312              :       }
     313              :     } catch (e, s) {
     314            0 :       Logs().e('[Bootstrapping] Error trying to migrate old secrets', e, s);
     315            0 :       state = BootstrapState.error;
     316              :       return;
     317              :     }
     318              :     // alright, we successfully migrated all secrets, if needed
     319              : 
     320            1 :     checkCrossSigning();
     321              :   }
     322              : 
     323            0 :   Future<void> openExistingSsss() async {
     324            0 :     final newSsssKey = this.newSsssKey;
     325            0 :     if (state != BootstrapState.openExistingSsss || newSsssKey == null) {
     326            0 :       throw BootstrapBadStateException();
     327              :     }
     328            0 :     if (!newSsssKey.isUnlocked) {
     329            0 :       throw BootstrapBadStateException('Key not unlocked');
     330              :     }
     331            0 :     Logs().v('Maybe cache all...');
     332            0 :     await newSsssKey.maybeCacheAll();
     333            0 :     checkCrossSigning();
     334              :   }
     335              : 
     336            1 :   void checkCrossSigning() {
     337              :     // so, let's see if we have cross signing set up
     338            3 :     if (encryption.crossSigning.enabled) {
     339              :       // cross signing present, ask for wipe
     340            1 :       state = BootstrapState.askWipeCrossSigning;
     341              :       return;
     342              :     }
     343              :     // no cross signing present
     344            1 :     state = BootstrapState.askSetupCrossSigning;
     345              :   }
     346              : 
     347            1 :   Future<void> wipeCrossSigning(bool wipe) async {
     348            2 :     if (state != BootstrapState.askWipeCrossSigning) {
     349            0 :       throw BootstrapBadStateException();
     350              :     }
     351              :     if (wipe) {
     352            1 :       state = BootstrapState.askSetupCrossSigning;
     353              :     } else {
     354            3 :       await client.dehydratedDeviceSetup(newSsssKey!);
     355            1 :       checkOnlineKeyBackup();
     356              :     }
     357              :   }
     358              : 
     359            1 :   Future<void> askSetupCrossSigning({
     360              :     bool setupMasterKey = false,
     361              :     bool setupSelfSigningKey = false,
     362              :     bool setupUserSigningKey = false,
     363              :   }) async {
     364            2 :     if (state != BootstrapState.askSetupCrossSigning) {
     365            0 :       throw BootstrapBadStateException();
     366              :     }
     367              :     if (!setupMasterKey && !setupSelfSigningKey && !setupUserSigningKey) {
     368            3 :       await client.dehydratedDeviceSetup(newSsssKey!);
     369            1 :       checkOnlineKeyBackup();
     370              :       return;
     371              :     }
     372            2 :     final userID = client.userID!;
     373              :     try {
     374              :       Uint8List masterSigningKey;
     375            1 :       final secretsToStore = <String, String>{};
     376              :       MatrixCrossSigningKey? masterKey;
     377              :       MatrixCrossSigningKey? selfSigningKey;
     378              :       MatrixCrossSigningKey? userSigningKey;
     379              :       String? masterPub;
     380              :       if (setupMasterKey) {
     381            1 :         final master = olm.PkSigning();
     382              :         try {
     383            1 :           masterSigningKey = master.generate_seed();
     384            1 :           masterPub = master.init_with_seed(masterSigningKey);
     385            1 :           final json = <String, dynamic>{
     386              :             'user_id': userID,
     387            1 :             'usage': ['master'],
     388            1 :             'keys': <String, dynamic>{
     389            1 :               'ed25519:$masterPub': masterPub,
     390              :             },
     391              :           };
     392            1 :           masterKey = MatrixCrossSigningKey.fromJson(json);
     393            1 :           secretsToStore[EventTypes.CrossSigningMasterKey] =
     394            1 :               base64.encode(masterSigningKey);
     395              :         } finally {
     396            1 :           master.free();
     397              :         }
     398              :       } else {
     399            0 :         Logs().v('Get stored key...');
     400            0 :         masterSigningKey = base64decodeUnpadded(
     401            0 :           await newSsssKey?.getStored(EventTypes.CrossSigningMasterKey) ?? '',
     402              :         );
     403            0 :         if (masterSigningKey.isEmpty) {
     404              :           // no master signing key :(
     405            0 :           throw BootstrapBadStateException('No master key');
     406              :         }
     407            0 :         final master = olm.PkSigning();
     408              :         try {
     409            0 :           masterPub = master.init_with_seed(masterSigningKey);
     410              :         } finally {
     411            0 :           master.free();
     412              :         }
     413              :       }
     414            1 :       String? sign(Map<String, dynamic> object) {
     415            1 :         final keyObj = olm.PkSigning();
     416              :         try {
     417            1 :           keyObj.init_with_seed(masterSigningKey);
     418              :           return keyObj
     419            3 :               .sign(String.fromCharCodes(canonicalJson.encode(object)));
     420              :         } finally {
     421            1 :           keyObj.free();
     422              :         }
     423              :       }
     424              : 
     425              :       if (setupSelfSigningKey) {
     426            1 :         final selfSigning = olm.PkSigning();
     427              :         try {
     428            1 :           final selfSigningPriv = selfSigning.generate_seed();
     429            1 :           final selfSigningPub = selfSigning.init_with_seed(selfSigningPriv);
     430            1 :           final json = <String, dynamic>{
     431              :             'user_id': userID,
     432            1 :             'usage': ['self_signing'],
     433            1 :             'keys': <String, dynamic>{
     434            1 :               'ed25519:$selfSigningPub': selfSigningPub,
     435              :             },
     436              :           };
     437            1 :           final signature = sign(json);
     438            2 :           json['signatures'] = <String, dynamic>{
     439            1 :             userID: <String, dynamic>{
     440            1 :               'ed25519:$masterPub': signature,
     441              :             },
     442              :           };
     443            1 :           selfSigningKey = MatrixCrossSigningKey.fromJson(json);
     444            1 :           secretsToStore[EventTypes.CrossSigningSelfSigning] =
     445            1 :               base64.encode(selfSigningPriv);
     446              :         } finally {
     447            1 :           selfSigning.free();
     448              :         }
     449              :       }
     450              :       if (setupUserSigningKey) {
     451            1 :         final userSigning = olm.PkSigning();
     452              :         try {
     453            1 :           final userSigningPriv = userSigning.generate_seed();
     454            1 :           final userSigningPub = userSigning.init_with_seed(userSigningPriv);
     455            1 :           final json = <String, dynamic>{
     456              :             'user_id': userID,
     457            1 :             'usage': ['user_signing'],
     458            1 :             'keys': <String, dynamic>{
     459            1 :               'ed25519:$userSigningPub': userSigningPub,
     460              :             },
     461              :           };
     462            1 :           final signature = sign(json);
     463            2 :           json['signatures'] = <String, dynamic>{
     464            1 :             userID: <String, dynamic>{
     465            1 :               'ed25519:$masterPub': signature,
     466              :             },
     467              :           };
     468            1 :           userSigningKey = MatrixCrossSigningKey.fromJson(json);
     469            1 :           secretsToStore[EventTypes.CrossSigningUserSigning] =
     470            1 :               base64.encode(userSigningPriv);
     471              :         } finally {
     472            1 :           userSigning.free();
     473              :         }
     474              :       }
     475              :       // upload the keys!
     476            1 :       state = BootstrapState.loading;
     477            2 :       Logs().v('Upload device signing keys.');
     478            2 :       await client.uiaRequestBackground(
     479            3 :         (AuthenticationData? auth) => client.uploadCrossSigningKeys(
     480              :           masterKey: masterKey,
     481              :           selfSigningKey: selfSigningKey,
     482              :           userSigningKey: userSigningKey,
     483              :           auth: auth,
     484              :         ),
     485              :       );
     486            2 :       Logs().v('Device signing keys have been uploaded.');
     487              :       // aaaand set the SSSS secrets
     488              :       if (masterKey != null) {
     489            1 :         while (!(masterKey.publicKey != null &&
     490            8 :             client.userDeviceKeys[client.userID]?.masterKey?.ed25519Key ==
     491            1 :                 masterKey.publicKey)) {
     492            0 :           Logs().v('Waiting for master to be created');
     493            0 :           await client.oneShotSync();
     494              :         }
     495              :       }
     496            1 :       if (newSsssKey != null) {
     497            1 :         final storeFutures = <Future<void>>[];
     498            2 :         for (final entry in secretsToStore.entries) {
     499            5 :           storeFutures.add(newSsssKey!.store(entry.key, entry.value));
     500              :         }
     501            2 :         Logs().v('Store new SSSS key entries...');
     502            1 :         await Future.wait(storeFutures);
     503              :       }
     504              : 
     505            1 :       final keysToSign = <SignableKey>[];
     506              :       if (masterKey != null) {
     507            8 :         if (client.userDeviceKeys[client.userID]?.masterKey?.ed25519Key !=
     508            1 :             masterKey.publicKey) {
     509            0 :           throw BootstrapBadStateException(
     510              :             'ERROR: New master key does not match up!',
     511              :           );
     512              :         }
     513            2 :         Logs().v('Set own master key to verified...');
     514            6 :         await client.userDeviceKeys[client.userID]!.masterKey!
     515            1 :             .setVerified(true, false);
     516            7 :         keysToSign.add(client.userDeviceKeys[client.userID]!.masterKey!);
     517              :       }
     518              :       if (selfSigningKey != null) {
     519            1 :         keysToSign.add(
     520            9 :           client.userDeviceKeys[client.userID]!.deviceKeys[client.deviceID]!,
     521              :         );
     522              :       }
     523            2 :       Logs().v('Sign ourself...');
     524            3 :       await encryption.crossSigning.sign(keysToSign);
     525              :     } catch (e, s) {
     526            0 :       Logs().e('[Bootstrapping] Error setting up cross signing', e, s);
     527            0 :       state = BootstrapState.error;
     528              :       return;
     529              :     }
     530              : 
     531            3 :     await client.dehydratedDeviceSetup(newSsssKey!);
     532            1 :     checkOnlineKeyBackup();
     533              :   }
     534              : 
     535            1 :   void checkOnlineKeyBackup() {
     536              :     // check if we have online key backup set up
     537            3 :     if (encryption.keyManager.enabled) {
     538            1 :       state = BootstrapState.askWipeOnlineKeyBackup;
     539              :       return;
     540              :     }
     541            1 :     state = BootstrapState.askSetupOnlineKeyBackup;
     542              :   }
     543              : 
     544            1 :   void wipeOnlineKeyBackup(bool wipe) {
     545            2 :     if (state != BootstrapState.askWipeOnlineKeyBackup) {
     546            0 :       throw BootstrapBadStateException();
     547              :     }
     548              :     if (wipe) {
     549            1 :       state = BootstrapState.askSetupOnlineKeyBackup;
     550              :     } else {
     551            1 :       state = BootstrapState.done;
     552              :     }
     553              :   }
     554              : 
     555            1 :   Future<void> askSetupOnlineKeyBackup(bool setup) async {
     556            2 :     if (state != BootstrapState.askSetupOnlineKeyBackup) {
     557            0 :       throw BootstrapBadStateException();
     558              :     }
     559              :     if (!setup) {
     560            1 :       state = BootstrapState.done;
     561              :       return;
     562              :     }
     563              :     try {
     564            1 :       final keyObj = olm.PkDecryption();
     565              :       String pubKey;
     566              :       Uint8List privKey;
     567              :       try {
     568            1 :         pubKey = keyObj.generate_key();
     569            1 :         privKey = keyObj.get_private_key();
     570              :       } finally {
     571            1 :         keyObj.free();
     572              :       }
     573            2 :       Logs().v('Create the new backup version...');
     574            2 :       await client.postRoomKeysVersion(
     575              :         BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2,
     576            1 :         <String, dynamic>{
     577              :           'public_key': pubKey,
     578              :         },
     579              :       );
     580            2 :       Logs().v('Store the secret...');
     581            3 :       await newSsssKey?.store(megolmKey, base64.encode(privKey));
     582              : 
     583            2 :       Logs().v(
     584              :         'And finally set all megolm keys as needing to be uploaded again...',
     585              :       );
     586            3 :       await client.database?.markInboundGroupSessionsAsNeedingUpload();
     587            2 :       Logs().v('And uploading keys...');
     588            4 :       await client.encryption?.keyManager.uploadInboundGroupSessions();
     589              :     } catch (e, s) {
     590            0 :       Logs().e('[Bootstrapping] Error setting up online key backup', e, s);
     591            0 :       state = BootstrapState.error;
     592            0 :       encryption.client.onEncryptionError.add(
     593            0 :         SdkError(exception: e, stackTrace: s),
     594              :       );
     595              :       return;
     596              :     }
     597            1 :     state = BootstrapState.done;
     598              :   }
     599              : 
     600            1 :   set state(BootstrapState newState) {
     601            3 :     Logs().v('BootstrapState: $newState');
     602            2 :     if (state != BootstrapState.error) {
     603            1 :       _state = newState;
     604              :     }
     605              : 
     606            2 :     onUpdate?.call(this);
     607              :   }
     608              : }
     609              : 
     610              : class BootstrapBadStateException implements Exception {
     611              :   String cause;
     612            0 :   BootstrapBadStateException([this.cause = 'Bad state']);
     613              : 
     614            0 :   @override
     615            0 :   String toString() => 'BootstrapBadStateException: $cause';
     616              : }
        

Generated by: LCOV version 2.0-1