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 760c7bbc0..5851505e6 100644 --- a/lib/core/backup.dart +++ b/lib/core/backup_service.dart @@ -14,128 +14,58 @@ 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, - {@required String nonce}) async { - final appDir = await getApplicationDocumentsDirectory(); - final decryptedData = await _decrypt(data, password, nonce); - final zip = ZipDecoder().decodeBytes(decryptedData); + {String nonce = secrets.backupSalt}) async { + final version = getVersion(data); + final backupBytes = data.toList()..removeAt(0); + final backupData = Uint8List.fromList(backupBytes); - zip.files.forEach((file) { - final filename = file.name; - - if (file.isFile) { - final data = file.content as List; - File('${appDir.path}/' + filename) - ..createSync(recursive: true) - ..writeAsBytesSync(data); - } else { - Directory('${appDir.path}/' + filename)..create(recursive: true); - } - - print(filename); - }); - - await importKeychainDump(password, nonce: nonce); - await importPreferencesDump(); - } - - Future importPreferencesDump() async { - final appDir = await getApplicationDocumentsDirectory(); - final preferencesFile = File('${appDir.path}/~_preferences_dump'); - - if (!preferencesFile.existsSync()) { - return; + switch (version) { + case _v1: + await _importBackupV1(backupData, password, nonce: nonce); + break; + default: + break; } - - final data = - json.decode(preferencesFile.readAsStringSync()) as Map; - print('data $data'); - - await _sharedPreferences.setString(PreferencesKey.currentWalletName, - data[PreferencesKey.currentWalletName] as String); - await _sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, - data[PreferencesKey.currentNodeIdKey] as int); - await _sharedPreferences.setInt(PreferencesKey.currentBalanceDisplayModeKey, - data[PreferencesKey.currentBalanceDisplayModeKey] as int); - await _sharedPreferences.setInt(PreferencesKey.currentWalletType, - data[PreferencesKey.currentWalletType] as int); - await _sharedPreferences.setString(PreferencesKey.currentFiatCurrencyKey, - data[PreferencesKey.currentFiatCurrencyKey] as String); - await _sharedPreferences.setBool( - PreferencesKey.shouldSaveRecipientAddressKey, - data[PreferencesKey.shouldSaveRecipientAddressKey] as bool); - await _sharedPreferences.setInt( - PreferencesKey.currentTransactionPriorityKey, - data[PreferencesKey.currentTransactionPriorityKey] as int); - await _sharedPreferences.setBool( - PreferencesKey.allowBiometricalAuthenticationKey, - data[PreferencesKey.allowBiometricalAuthenticationKey] as bool); - await _sharedPreferences.setInt( - PreferencesKey.currentBitcoinElectrumSererIdKey, - data[PreferencesKey.currentBitcoinElectrumSererIdKey] as int); - await _sharedPreferences.setInt(PreferencesKey.currentLanguageCode, - data[PreferencesKey.currentLanguageCode] as int); - await _sharedPreferences.setInt(PreferencesKey.displayActionListModeKey, - data[PreferencesKey.displayActionListModeKey] as int); - await _sharedPreferences.setInt( - 'current_theme', data['current_theme'] as int); - - await preferencesFile.delete(); - } - - Future importKeychainDump(String password, - {@required String nonce}) async { - final appDir = await getApplicationDocumentsDirectory(); - final keychainDumpFile = File('${appDir.path}/~_keychain_dump'); - final decryptedKeychainDumpFileData = - await _decrypt(keychainDumpFile.readAsBytesSync(), 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); - - keychainWalletsInfo.forEach((dynamic rawInfo) async { - final info = rawInfo as Map; - await importWalletKeychainInfo(info); - }); - - await _flutterSecureStorage.write( - key: pinCodeKey, value: encodedPinCode(pin: decodedPin)); - - keychainDumpFile.deleteSync(); - } - - Future importWalletKeychainInfo(Map info) async { - final name = info['name'] as String; - final password = info['password'] as String; - - await _keyService.saveWalletPassword(walletName: name, password: password); } Future exportBackup(String password, - {@required String nonce}) async { + {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 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'); @@ -165,12 +95,120 @@ class BackupService { final content = File(archivePath).readAsBytesSync(); tmpDir.deleteSync(recursive: true); + final encryptedData = await _encrypt(content, password, nonce); - return await _encrypt(content, password, nonce); + return setVersion(encryptedData, currentVersion); } - Future exportKeychainDump(String password, + Future _importBackupV1(Uint8List data, String password, {@required String nonce}) async { + final appDir = await getApplicationDocumentsDirectory(); + final decryptedData = await _decrypt(data, password, nonce); + final zip = ZipDecoder().decodeBytes(decryptedData); + + zip.files.forEach((file) { + final filename = file.name; + + if (file.isFile) { + final content = file.content as List; + File('${appDir.path}/' + filename) + ..createSync(recursive: true) + ..writeAsBytesSync(content); + } else { + Directory('${appDir.path}/' + filename)..create(recursive: true); + } + }); + + await _importKeychainDump(password, nonce: nonce); + await _importPreferencesDump(); + } + + Future _importPreferencesDump() async { + final appDir = await getApplicationDocumentsDirectory(); + final preferencesFile = File('${appDir.path}/~_preferences_dump'); + const defaultSettingsMigrationVersionKey = PreferencesKey.currentDefaultSettingsMigrationVersion; + + if (!preferencesFile.existsSync()) { + return; + } + + final data = + json.decode(preferencesFile.readAsStringSync()) as Map; + + await _sharedPreferences.setString(PreferencesKey.currentWalletName, + data[PreferencesKey.currentWalletName] as String); + await _sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, + data[PreferencesKey.currentNodeIdKey] as int); + await _sharedPreferences.setInt(PreferencesKey.currentBalanceDisplayModeKey, + data[PreferencesKey.currentBalanceDisplayModeKey] as int); + await _sharedPreferences.setInt(PreferencesKey.currentWalletType, + data[PreferencesKey.currentWalletType] as int); + await _sharedPreferences.setString(PreferencesKey.currentFiatCurrencyKey, + data[PreferencesKey.currentFiatCurrencyKey] as String); + await _sharedPreferences.setBool( + PreferencesKey.shouldSaveRecipientAddressKey, + data[PreferencesKey.shouldSaveRecipientAddressKey] as bool); + await _sharedPreferences.setInt( + PreferencesKey.currentTransactionPriorityKey, + data[PreferencesKey.currentTransactionPriorityKey] as int); + await _sharedPreferences.setBool( + PreferencesKey.allowBiometricalAuthenticationKey, + data[PreferencesKey.allowBiometricalAuthenticationKey] as bool); + await _sharedPreferences.setInt( + PreferencesKey.currentBitcoinElectrumSererIdKey, + data[PreferencesKey.currentBitcoinElectrumSererIdKey] as int); + await _sharedPreferences.setInt(PreferencesKey.currentLanguageCode, + data[PreferencesKey.currentLanguageCode] as int); + await _sharedPreferences.setInt(PreferencesKey.displayActionListModeKey, + 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, + String keychainSalt = secrets.backupKeychainSalt}) async { + final appDir = await getApplicationDocumentsDirectory(); + final keychainDumpFile = File('${appDir.path}/~_keychain_dump'); + 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; + await importWalletKeychainInfo(info); + }); + + await _flutterSecureStorage.write( + key: pinCodeKey, value: encodedPinCode(pin: decodedPin)); + + keychainDumpFile.deleteSync(); + } + + Future importWalletKeychainInfo(Map info) async { + final name = info['name'] as String; + final password = info['password'] as String; + + await _keyService.saveWalletPassword(walletName: name, password: password); + } + + 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 eddffc232..2f12d0bc3 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