LCOV - code coverage report
Current view: top level - lib/encryption/utils - bootstrap.dart (source / functions) Hit Total Coverage
Test: merged.info Lines: 231 283 81.6 %
Date: 2024-11-12 07:37:08 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 1.14