From 47ceac2dd6101a1071c2afb5486f03747eb1351e Mon Sep 17 00:00:00 2001 From: M Date: Fri, 15 Jan 2021 19:41:30 +0200 Subject: [PATCH] Backup stuff. --- lib/core/{backup.dart => backup_service.dart} | 191 ++++++++++++------ lib/di.dart | 79 +++++--- lib/entities/contact.dart | 3 +- lib/entities/default_settings_migration.dart | 29 ++- lib/entities/node.dart | 3 +- lib/entities/preferences_key.dart | 1 + lib/entities/template.dart | 3 +- lib/entities/transaction_description.dart | 3 +- lib/entities/wallet_info.dart | 3 +- lib/entities/wallet_type.dart | 3 +- lib/exchange/exchange_template.dart | 3 +- lib/exchange/trade.dart | 3 +- lib/main.dart | 99 +++++---- lib/reactions/bootstrap.dart | 14 +- lib/router.dart | 3 +- lib/src/screens/backup/backup_page.dart | 22 +- .../restore/restore_from_backup_page.dart | 1 + .../screens/restore/restore_options_page.dart | 59 +++--- lib/src/screens/welcome/welcome_page.dart | 2 +- lib/src/widgets/trail_button.dart | 2 +- lib/store/settings_store.dart | 33 ++- lib/view_model/backup_view_model.dart | 5 +- .../restore_from_backup_view_model.dart | 51 +++-- .../settings/settings_view_model.dart | 13 ++ 24 files changed, 417 insertions(+), 211 deletions(-) rename lib/core/{backup.dart => backup_service.dart} (72%) diff --git a/lib/core/backup.dart b/lib/core/backup_service.dart similarity index 72% rename from lib/core/backup.dart rename to lib/core/backup_service.dart index 760c7bbc..5851505e 100644 --- a/lib/core/backup.dart +++ b/lib/core/backup_service.dart @@ -14,20 +14,93 @@ import 'package:cake_wallet/entities/encrypt.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cake_wallet/entities/wallet_info.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; class BackupService { - BackupService(this._flutterSecureStorage, this._authService, - this._walletInfoSource, this._keyService, this._sharedPreferences) + BackupService(this._flutterSecureStorage, this._walletInfoSource, + this._keyService, this._sharedPreferences) : _cipher = chacha20Poly1305Aead; + static const currentVersion = _v1; + + static const _v1 = 1; + final Cipher _cipher; final FlutterSecureStorage _flutterSecureStorage; final SharedPreferences _sharedPreferences; - final AuthService _authService; final Box _walletInfoSource; final KeyService _keyService; Future importBackup(Uint8List data, String password, + {String nonce = secrets.backupSalt}) async { + final version = getVersion(data); + final backupBytes = data.toList()..removeAt(0); + final backupData = Uint8List.fromList(backupBytes); + + switch (version) { + case _v1: + await _importBackupV1(backupData, password, nonce: nonce); + break; + default: + break; + } + } + + Future exportBackup(String password, + {String nonce = secrets.backupSalt, int version = currentVersion}) async { + switch (version) { + case _v1: + return await _exportBackupV1(password, nonce: nonce); + default: + return null; + } + } + + Future _exportBackupV1(String password, + {String nonce = secrets.backupSalt}) async { + final zipEncoder = ZipFileEncoder(); + final appDir = await getApplicationDocumentsDirectory(); + final now = DateTime.now(); + final tmpDir = Directory('${appDir.path}/~_BACKUP_TMP'); + final archivePath = '${tmpDir.path}/backup_${now.toString()}.zip'; + final fileEntities = appDir.listSync(recursive: false); + final keychainDump = await _exportKeychainDump(password, nonce: nonce); + final preferencesDump = await _exportPreferencesJSON(); + final preferencesDumpFile = File('${tmpDir.path}/~_preferences_dump_TMP'); + final keychainDumpFile = File('${tmpDir.path}/~_keychain_dump_TMP'); + + if (tmpDir.existsSync()) { + tmpDir.deleteSync(recursive: true); + } + + tmpDir.createSync(); + zipEncoder.create(archivePath); + + fileEntities.forEach((entity) { + if (entity.path == archivePath || entity.path == tmpDir.path) { + return; + } + + if (entity.statSync().type == FileSystemEntityType.directory) { + zipEncoder.addDirectory(Directory(entity.path)); + } else { + zipEncoder.addFile(File(entity.path)); + } + }); + await keychainDumpFile.writeAsBytes(keychainDump.toList()); + await preferencesDumpFile.writeAsString(preferencesDump); + zipEncoder.addFile(preferencesDumpFile, '~_preferences_dump'); + zipEncoder.addFile(keychainDumpFile, '~_keychain_dump'); + zipEncoder.close(); + + final content = File(archivePath).readAsBytesSync(); + tmpDir.deleteSync(recursive: true); + final encryptedData = await _encrypt(content, password, nonce); + + return setVersion(encryptedData, currentVersion); + } + + Future _importBackupV1(Uint8List data, String password, {@required String nonce}) async { final appDir = await getApplicationDocumentsDirectory(); final decryptedData = await _decrypt(data, password, nonce); @@ -37,24 +110,23 @@ class BackupService { final filename = file.name; if (file.isFile) { - final data = file.content as List; + final content = file.content as List; File('${appDir.path}/' + filename) ..createSync(recursive: true) - ..writeAsBytesSync(data); + ..writeAsBytesSync(content); } else { Directory('${appDir.path}/' + filename)..create(recursive: true); } - - print(filename); }); - await importKeychainDump(password, nonce: nonce); - await importPreferencesDump(); + await _importKeychainDump(password, nonce: nonce); + await _importPreferencesDump(); } - Future importPreferencesDump() async { + Future _importPreferencesDump() async { final appDir = await getApplicationDocumentsDirectory(); final preferencesFile = File('${appDir.path}/~_preferences_dump'); + const defaultSettingsMigrationVersionKey = PreferencesKey.currentDefaultSettingsMigrationVersion; if (!preferencesFile.existsSync()) { return; @@ -62,7 +134,6 @@ class BackupService { final data = json.decode(preferencesFile.readAsStringSync()) as Map; - print('data $data'); await _sharedPreferences.setString(PreferencesKey.currentWalletName, data[PreferencesKey.currentWalletName] as String); @@ -92,21 +163,30 @@ class BackupService { data[PreferencesKey.displayActionListModeKey] as int); await _sharedPreferences.setInt( 'current_theme', data['current_theme'] as int); + await _sharedPreferences.setInt(defaultSettingsMigrationVersionKey, + data[defaultSettingsMigrationVersionKey] as int); await preferencesFile.delete(); } - Future importKeychainDump(String password, - {@required String nonce}) async { + Future _importKeychainDump(String password, + {@required String nonce, + String keychainSalt = secrets.backupKeychainSalt}) async { final appDir = await getApplicationDocumentsDirectory(); final keychainDumpFile = File('${appDir.path}/~_keychain_dump'); - final decryptedKeychainDumpFileData = - await _decrypt(keychainDumpFile.readAsBytesSync(), password, nonce); + final decryptedKeychainDumpFileData = await _decrypt( + keychainDumpFile.readAsBytesSync(), '$keychainSalt$password', nonce); final keychainJSON = json.decode(utf8.decode(decryptedKeychainDumpFileData)) as Map; final keychainWalletsInfo = keychainJSON['wallets'] as List; final decodedPin = keychainJSON['pin'] as String; final pinCodeKey = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); + final backupPasswordKey = + generateStoreKeyFor(key: SecretStoreKey.backupPassword); + final backupPassword = keychainJSON[backupPasswordKey] as String; + + await _flutterSecureStorage.write( + key: backupPasswordKey, value: backupPassword); keychainWalletsInfo.forEach((dynamic rawInfo) async { final info = rawInfo as Map; @@ -126,51 +206,9 @@ class BackupService { await _keyService.saveWalletPassword(walletName: name, password: password); } - Future exportBackup(String password, - {@required String nonce}) async { - final zipEncoder = ZipFileEncoder(); - final appDir = await getApplicationDocumentsDirectory(); - final now = DateTime.now(); - final tmpDir = Directory('${appDir.path}/~_BACKUP_TMP'); - final archivePath = '${tmpDir.path}/backup_${now.toString()}.zip'; - final fileEntities = appDir.listSync(recursive: false); - final keychainDump = await exportKeychainDump(password, nonce: nonce); - final preferencesDump = await exportPreferencesJSON(); - final preferencesDumpFile = File('${tmpDir.path}/~_preferences_dump_TMP'); - final keychainDumpFile = File('${tmpDir.path}/~_keychain_dump_TMP'); - - if (tmpDir.existsSync()) { - tmpDir.deleteSync(recursive: true); - } - - tmpDir.createSync(); - zipEncoder.create(archivePath); - - fileEntities.forEach((entity) { - if (entity.path == archivePath || entity.path == tmpDir.path) { - return; - } - - if (entity.statSync().type == FileSystemEntityType.directory) { - zipEncoder.addDirectory(Directory(entity.path)); - } else { - zipEncoder.addFile(File(entity.path)); - } - }); - await keychainDumpFile.writeAsBytes(keychainDump.toList()); - await preferencesDumpFile.writeAsString(preferencesDump); - zipEncoder.addFile(preferencesDumpFile, '~_preferences_dump'); - zipEncoder.addFile(keychainDumpFile, '~_keychain_dump'); - zipEncoder.close(); - - final content = File(archivePath).readAsBytesSync(); - tmpDir.deleteSync(recursive: true); - - return await _encrypt(content, password, nonce); - } - - Future exportKeychainDump(String password, - {@required String nonce}) async { + Future _exportKeychainDump(String password, + {@required String nonce, + String keychainSalt = secrets.backupKeychainSalt}) async { final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); final encodedPin = await _flutterSecureStorage.read(key: key); final decodedPin = decodedPinCode(pin: encodedPin); @@ -183,15 +221,25 @@ class BackupService { await _keyService.getWalletPassword(walletName: walletInfo.name) }; })); - - final data = - utf8.encode(json.encode({'pin': decodedPin, 'wallets': wallets})); - final encrypted = await _encrypt(Uint8List.fromList(data), password, nonce); + final backupPasswordKey = + generateStoreKeyFor(key: SecretStoreKey.backupPassword); + final backupPassword = + await _flutterSecureStorage.read(key: backupPasswordKey); + final data = utf8.encode(json.encode({ + 'pin': decodedPin, + 'wallets': wallets, + backupPasswordKey: backupPassword + })); + final encrypted = await _encrypt( + Uint8List.fromList(data), '$keychainSalt$password', nonce); return encrypted; } - Future exportPreferencesJSON() async { + Future _exportPreferencesJSON() async { + const defaultSettingsMigrationVersionKey = + 'current_default_settings_migration_version'; + final preferences = { PreferencesKey.currentWalletName: _sharedPreferences.getString(PreferencesKey.currentWalletName), @@ -219,13 +267,22 @@ class BackupService { _sharedPreferences.getString(PreferencesKey.currentLanguageCode), PreferencesKey.displayActionListModeKey: _sharedPreferences.getInt(PreferencesKey.displayActionListModeKey), - PreferencesKey.currentTheme: _sharedPreferences.getInt(PreferencesKey.currentTheme) - // FIX-ME: Unnamed constant. + PreferencesKey.currentTheme: + _sharedPreferences.getInt(PreferencesKey.currentTheme), + defaultSettingsMigrationVersionKey: + _sharedPreferences.getInt(defaultSettingsMigrationVersionKey) }; return json.encode(preferences); } + int getVersion(Uint8List data) => data.toList().first; + + Uint8List setVersion(Uint8List data, int version) { + final bytes = data.toList()..insert(0, version); + return Uint8List.fromList(bytes); + } + Future _encrypt( Uint8List data, String secretKeySource, String nonceBase64) async { final secretKeyHash = await sha256.hash(utf8.encode(secretKeySource)); diff --git a/lib/di.dart b/lib/di.dart index eddffc23..2f12d0bc 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1,5 +1,5 @@ import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart'; -import 'package:cake_wallet/core/backup.dart'; +import 'package:cake_wallet/core/backup_service.dart'; import 'package:cake_wallet/core/wallet_service.dart'; import 'package:cake_wallet/entities/biometric_auth.dart'; import 'package:cake_wallet/entities/contact_record.dart'; @@ -105,6 +105,15 @@ import 'package:cake_wallet/exchange/exchange_template.dart'; final getIt = GetIt.instance; +var _isSetupFinished = false; +Box _walletInfoSource; +Box _nodeSource; +Box _contactSource; +Box _tradesSource; +Box