configuration.dart 11 KB

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