configuration.dart 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. import 'dart:convert';
  2. import 'dart:io' as io;
  3. import 'dart:typed_data';
  4. import 'package:flutter/foundation.dart';
  5. import 'package:flutter_secure_storage/flutter_secure_storage.dart';
  6. import 'package:flutter_sodium/flutter_sodium.dart';
  7. import 'package:logging/logging.dart';
  8. import 'package:path_provider/path_provider.dart';
  9. import 'package:photos/core/event_bus.dart';
  10. import 'package:photos/db/collections_db.dart';
  11. import 'package:photos/db/files_db.dart';
  12. import 'package:photos/db/memories_db.dart';
  13. import 'package:photos/db/public_keys_db.dart';
  14. import 'package:photos/db/upload_locks_db.dart';
  15. import 'package:photos/events/user_logged_out_event.dart';
  16. import 'package:photos/models/key_attributes.dart';
  17. import 'package:photos/models/key_gen_result.dart';
  18. import 'package:photos/models/private_key_attributes.dart';
  19. import 'package:photos/services/billing_service.dart';
  20. import 'package:photos/services/collections_service.dart';
  21. import 'package:photos/services/favorites_service.dart';
  22. import 'package:photos/services/memories_service.dart';
  23. import 'package:photos/services/sync_service.dart';
  24. import 'package:shared_preferences/shared_preferences.dart';
  25. import 'package:photos/utils/crypto_util.dart';
  26. class Configuration {
  27. Configuration._privateConstructor();
  28. static final Configuration instance = Configuration._privateConstructor();
  29. static final _logger = Logger("Configuration");
  30. final kTempFolderDeletionTimeBuffer = Duration(days: 1).inMicroseconds;
  31. static const userIDKey = "user_id";
  32. static const emailKey = "email";
  33. static const nameKey = "name";
  34. static const tokenKey = "token";
  35. static const foldersToBackUpKey = "folders_to_back_up";
  36. static const keyKey = "key";
  37. static const secretKeyKey = "secret_key";
  38. static const keyAttributesKey = "key_attributes";
  39. static const keyShouldBackupOverMobileData = "should_backup_over_mobile_data";
  40. static const keyShouldShowLockScreen = "should_show_lock_screen";
  41. static const keyShouldHideFromRecents = "should_hide_from_recents";
  42. static const lastTempFolderClearTimeKey = "last_temp_folder_clear_time";
  43. SharedPreferences _preferences;
  44. FlutterSecureStorage _secureStorage;
  45. String _key;
  46. String _cachedToken;
  47. String _secretKey;
  48. String _documentsDirectory;
  49. String _tempDirectory;
  50. Future<void> init() async {
  51. _preferences = await SharedPreferences.getInstance();
  52. _secureStorage = FlutterSecureStorage();
  53. _documentsDirectory = (await getApplicationDocumentsDirectory()).path;
  54. _tempDirectory = _documentsDirectory + "/temp/";
  55. final tempDirectory = new io.Directory(_tempDirectory);
  56. try {
  57. final currentTime = DateTime.now().microsecondsSinceEpoch;
  58. if (tempDirectory.existsSync() &&
  59. (_preferences.getInt(lastTempFolderClearTimeKey) ?? 0) <
  60. (currentTime - kTempFolderDeletionTimeBuffer)) {
  61. tempDirectory.deleteSync(recursive: true);
  62. await _preferences.setInt(lastTempFolderClearTimeKey, currentTime);
  63. _logger.info("Cleared temp folder");
  64. } else {
  65. _logger.info("Skipping temp folder clear");
  66. }
  67. } catch (e) {
  68. _logger.warning(e);
  69. }
  70. tempDirectory.createSync(recursive: true);
  71. if (!_preferences.containsKey(tokenKey)) {
  72. await _secureStorage.deleteAll();
  73. } else {
  74. _key = await _secureStorage.read(key: keyKey);
  75. _secretKey = await _secureStorage.read(key: secretKeyKey);
  76. }
  77. }
  78. Future<void> logout() async {
  79. if (SyncService.instance.isSyncInProgress()) {
  80. SyncService.instance.stopSync();
  81. try {
  82. await SyncService.instance.existingSync();
  83. } catch (e) {
  84. // ignore
  85. }
  86. }
  87. await _preferences.clear();
  88. await _secureStorage.deleteAll();
  89. _key = null;
  90. _cachedToken = null;
  91. _secretKey = null;
  92. await FilesDB.instance.clearTable();
  93. await CollectionsDB.instance.clearTable();
  94. await MemoriesDB.instance.clearTable();
  95. await PublicKeysDB.instance.clearTable();
  96. await UploadLocksDB.instance.clearTable();
  97. CollectionsService.instance.clearCache();
  98. FavoritesService.instance.clearCache();
  99. MemoriesService.instance.clearCache();
  100. BillingService.instance.clearCache();
  101. Bus.instance.fire(UserLoggedOutEvent());
  102. }
  103. Future<KeyGenResult> generateKey(String password) async {
  104. // Create a master key
  105. final key = CryptoUtil.generateKey();
  106. // Derive a key from the password that will be used to encrypt and
  107. // decrypt the master key
  108. final kekSalt = CryptoUtil.getSaltToDeriveKey();
  109. int memLimit = Sodium.cryptoPwhashMemlimitSensitive;
  110. final opsLimit = Sodium.cryptoPwhashOpslimitSensitive;
  111. var kek;
  112. try {
  113. kek = await CryptoUtil.deriveKey(
  114. utf8.encode(password),
  115. kekSalt,
  116. memLimit,
  117. opsLimit,
  118. );
  119. } catch (e) {
  120. _logger.info("Reducing memory utilization");
  121. memLimit = Sodium.cryptoPwhashMemlimitModerate;
  122. kek = await CryptoUtil.deriveKey(
  123. utf8.encode(password),
  124. kekSalt,
  125. memLimit,
  126. opsLimit,
  127. );
  128. }
  129. // Encrypt the key with this derived key
  130. final encryptedKeyData = CryptoUtil.encryptSync(key, kek);
  131. // Generate a public-private keypair and encrypt the latter
  132. final keyPair = await CryptoUtil.generateKeyPair();
  133. final encryptedSecretKeyData = CryptoUtil.encryptSync(keyPair.sk, key);
  134. final attributes = KeyAttributes(
  135. Sodium.bin2base64(kekSalt),
  136. Sodium.bin2base64(encryptedKeyData.encryptedData),
  137. Sodium.bin2base64(encryptedKeyData.nonce),
  138. Sodium.bin2base64(keyPair.pk),
  139. Sodium.bin2base64(encryptedSecretKeyData.encryptedData),
  140. Sodium.bin2base64(encryptedSecretKeyData.nonce),
  141. memLimit,
  142. opsLimit,
  143. );
  144. final privateAttributes = PrivateKeyAttributes(
  145. Sodium.bin2base64(key), Sodium.bin2base64(keyPair.sk));
  146. return KeyGenResult(attributes, privateAttributes);
  147. }
  148. Future<void> decryptAndSaveKey(
  149. String password, KeyAttributes attributes) async {
  150. final kek = await CryptoUtil.deriveKey(
  151. utf8.encode(password),
  152. Sodium.base642bin(attributes.kekSalt),
  153. attributes.memLimit,
  154. attributes.opsLimit,
  155. );
  156. var key;
  157. try {
  158. key = CryptoUtil.decryptSync(Sodium.base642bin(attributes.encryptedKey),
  159. kek, Sodium.base642bin(attributes.keyDecryptionNonce));
  160. } catch (e) {
  161. throw Exception("Incorrect password");
  162. }
  163. final secretKey = CryptoUtil.decryptSync(
  164. Sodium.base642bin(attributes.encryptedSecretKey),
  165. key,
  166. Sodium.base642bin(attributes.secretKeyDecryptionNonce));
  167. await setKey(Sodium.bin2base64(key));
  168. await setSecretKey(Sodium.bin2base64(secretKey));
  169. }
  170. String getHttpEndpoint() {
  171. if (kDebugMode) {
  172. return "http://192.168.1.111:8080";
  173. }
  174. return "https://api.ente.io";
  175. }
  176. String getToken() {
  177. if (_cachedToken == null) {
  178. _cachedToken = _preferences.getString(tokenKey);
  179. }
  180. return _cachedToken;
  181. }
  182. Future<void> setToken(String token) async {
  183. _cachedToken = token;
  184. await _preferences.setString(tokenKey, token);
  185. }
  186. String getEmail() {
  187. return _preferences.getString(emailKey);
  188. }
  189. Future<void> setEmail(String email) async {
  190. await _preferences.setString(emailKey, email);
  191. }
  192. String getName() {
  193. return _preferences.getString(nameKey);
  194. }
  195. Future<void> setName(String name) async {
  196. await _preferences.setString(nameKey, name);
  197. }
  198. int getUserID() {
  199. return _preferences.getInt(userIDKey);
  200. }
  201. Future<void> setUserID(int userID) async {
  202. await _preferences.setInt(userIDKey, userID);
  203. }
  204. Set<String> getPathsToBackUp() {
  205. if (_preferences.containsKey(foldersToBackUpKey)) {
  206. return _preferences.getStringList(foldersToBackUpKey).toSet();
  207. } else {
  208. return Set<String>();
  209. }
  210. }
  211. Future<void> setPathsToBackUp(Set<String> folders) async {
  212. bool shouldSync =
  213. !listEquals(getPathsToBackUp().toList(), folders.toList());
  214. await _preferences.setStringList(foldersToBackUpKey, folders.toList());
  215. if (shouldSync) {
  216. SyncService.instance.sync();
  217. }
  218. }
  219. Future<void> addPathToFoldersToBeBackedUp(String path) async {
  220. final currentPaths = getPathsToBackUp();
  221. currentPaths.add(path);
  222. return setPathsToBackUp(currentPaths);
  223. }
  224. Future<void> setKeyAttributes(KeyAttributes attributes) async {
  225. await _preferences.setString(
  226. keyAttributesKey, attributes == null ? null : attributes.toJson());
  227. }
  228. KeyAttributes getKeyAttributes() {
  229. final jsonValue = _preferences.getString(keyAttributesKey);
  230. if (jsonValue == null) {
  231. return null;
  232. } else {
  233. return KeyAttributes.fromJson(jsonValue);
  234. }
  235. }
  236. Future<void> setKey(String key) async {
  237. _key = key;
  238. if (key == null) {
  239. await _secureStorage.delete(key: keyKey);
  240. } else {
  241. await _secureStorage.write(key: keyKey, value: key);
  242. }
  243. }
  244. Future<void> setSecretKey(String secretKey) async {
  245. _secretKey = secretKey;
  246. if (secretKey == null) {
  247. await _secureStorage.delete(key: secretKeyKey);
  248. } else {
  249. await _secureStorage.write(key: secretKeyKey, value: secretKey);
  250. }
  251. }
  252. Uint8List getKey() {
  253. return _key == null ? null : Sodium.base642bin(_key);
  254. }
  255. Uint8List getSecretKey() {
  256. return _secretKey == null ? null : Sodium.base642bin(_secretKey);
  257. }
  258. String getDocumentsDirectory() {
  259. return _documentsDirectory;
  260. }
  261. // Caution: This directory is cleared on app start
  262. String getTempDirectory() {
  263. return _tempDirectory;
  264. }
  265. bool hasConfiguredAccount() {
  266. return getToken() != null && _key != null;
  267. }
  268. bool shouldBackupOverMobileData() {
  269. if (_preferences.containsKey(keyShouldBackupOverMobileData)) {
  270. return _preferences.getBool(keyShouldBackupOverMobileData);
  271. } else {
  272. return false;
  273. }
  274. }
  275. Future<void> setBackupOverMobileData(bool value) async {
  276. await _preferences.setBool(keyShouldBackupOverMobileData, value);
  277. if (value) {
  278. SyncService.instance.sync();
  279. }
  280. }
  281. bool shouldShowLockScreen() {
  282. if (_preferences.containsKey(keyShouldShowLockScreen)) {
  283. return _preferences.getBool(keyShouldShowLockScreen);
  284. } else {
  285. return false;
  286. }
  287. }
  288. Future<void> setShouldShowLockScreen(bool value) {
  289. return _preferences.setBool(keyShouldShowLockScreen, value);
  290. }
  291. bool shouldHideFromRecents() {
  292. if (_preferences.containsKey(keyShouldHideFromRecents)) {
  293. return _preferences.getBool(keyShouldHideFromRecents);
  294. } else {
  295. return false;
  296. }
  297. }
  298. Future<void> setShouldHideFromRecents(bool value) {
  299. return _preferences.setBool(keyShouldHideFromRecents, value);
  300. }
  301. }