configuration.dart 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:io' as io;
  4. import 'dart:typed_data';
  5. import 'package:bip39/bip39.dart' as bip39;
  6. import 'package:ente_auth/core/constants.dart';
  7. import 'package:ente_auth/core/event_bus.dart';
  8. import 'package:ente_auth/events/endpoint_updated_event.dart';
  9. import 'package:ente_auth/events/signed_in_event.dart';
  10. import 'package:ente_auth/events/signed_out_event.dart';
  11. import 'package:ente_auth/models/key_attributes.dart';
  12. import 'package:ente_auth/models/key_gen_result.dart';
  13. import 'package:ente_auth/models/private_key_attributes.dart';
  14. import 'package:ente_auth/store/authenticator_db.dart';
  15. import 'package:ente_crypto_dart/ente_crypto_dart.dart';
  16. import 'package:flutter_secure_storage/flutter_secure_storage.dart';
  17. import 'package:logging/logging.dart';
  18. import 'package:path_provider/path_provider.dart';
  19. import 'package:shared_preferences/shared_preferences.dart';
  20. import 'package:sqflite_common_ffi/sqflite_ffi.dart';
  21. import 'package:tuple/tuple.dart';
  22. class Configuration {
  23. Configuration._privateConstructor();
  24. static final Configuration instance = Configuration._privateConstructor();
  25. static const endpoint = String.fromEnvironment(
  26. "endpoint",
  27. defaultValue: kDefaultProductionEndpoint,
  28. );
  29. static const emailKey = "email";
  30. static const keyAttributesKey = "key_attributes";
  31. static const keyShouldShowLockScreen = "should_show_lock_screen";
  32. static const lastTempFolderClearTimeKey = "last_temp_folder_clear_time";
  33. static const keyKey = "key";
  34. static const secretKeyKey = "secret_key";
  35. static const authSecretKeyKey = "auth_secret_key";
  36. static const offlineAuthSecretKey = "offline_auth_secret_key";
  37. static const tokenKey = "token";
  38. static const encryptedTokenKey = "encrypted_token";
  39. static const userIDKey = "user_id";
  40. static const hasMigratedSecureStorageKey = "has_migrated_secure_storage";
  41. static const hasOptedForOfflineModeKey = "has_opted_for_offline_mode";
  42. static const endPointKey = "endpoint";
  43. final List<String> onlineSecureKeys = [
  44. keyKey,
  45. secretKeyKey,
  46. authSecretKeyKey,
  47. ];
  48. final kTempFolderDeletionTimeBuffer = const Duration(days: 1).inMicroseconds;
  49. static final _logger = Logger("Configuration");
  50. String? _cachedToken;
  51. late String _documentsDirectory;
  52. late SharedPreferences _preferences;
  53. String? _key;
  54. String? _secretKey;
  55. String? _authSecretKey;
  56. String? _offlineAuthKey;
  57. late FlutterSecureStorage _secureStorage;
  58. late String _tempDirectory;
  59. String? _volatilePassword;
  60. final _secureStorageOptionsIOS = const IOSOptions(
  61. accessibility: KeychainAccessibility.first_unlock_this_device,
  62. );
  63. Future<void> init() async {
  64. _preferences = await SharedPreferences.getInstance();
  65. sqfliteFfiInit();
  66. _secureStorage = const FlutterSecureStorage();
  67. _documentsDirectory = (await getApplicationDocumentsDirectory()).path;
  68. _tempDirectory = "$_documentsDirectory/temp/";
  69. final tempDirectory = io.Directory(_tempDirectory);
  70. try {
  71. final currentTime = DateTime.now().microsecondsSinceEpoch;
  72. if (tempDirectory.existsSync() &&
  73. (_preferences.getInt(lastTempFolderClearTimeKey) ?? 0) <
  74. (currentTime - kTempFolderDeletionTimeBuffer)) {
  75. await tempDirectory.delete(recursive: true);
  76. await _preferences.setInt(lastTempFolderClearTimeKey, currentTime);
  77. _logger.info("Cleared temp folder");
  78. } else {
  79. _logger.info("Skipping temp folder clear");
  80. }
  81. } catch (e) {
  82. _logger.warning(e);
  83. }
  84. tempDirectory.createSync(recursive: true);
  85. await _initOnlineAccount();
  86. await _initOfflineAccount();
  87. }
  88. Future<void> _initOfflineAccount() async {
  89. _offlineAuthKey = await _secureStorage.read(
  90. key: offlineAuthSecretKey,
  91. iOptions: _secureStorageOptionsIOS,
  92. );
  93. }
  94. Future<void> _initOnlineAccount() async {
  95. if (!_preferences.containsKey(tokenKey)) {
  96. for (final key in onlineSecureKeys) {
  97. unawaited(
  98. _secureStorage.delete(
  99. key: key,
  100. iOptions: _secureStorageOptionsIOS,
  101. ),
  102. );
  103. }
  104. } else {
  105. _key = await _secureStorage.read(
  106. key: keyKey,
  107. iOptions: _secureStorageOptionsIOS,
  108. );
  109. _secretKey = await _secureStorage.read(
  110. key: secretKeyKey,
  111. iOptions: _secureStorageOptionsIOS,
  112. );
  113. _authSecretKey = await _secureStorage.read(
  114. key: authSecretKeyKey,
  115. iOptions: _secureStorageOptionsIOS,
  116. );
  117. if (_key == null) {
  118. await logout(autoLogout: true);
  119. }
  120. }
  121. }
  122. Future<void> logout({bool autoLogout = false}) async {
  123. await _preferences.clear();
  124. for (String key in onlineSecureKeys) {
  125. await _secureStorage.delete(
  126. key: key,
  127. iOptions: _secureStorageOptionsIOS,
  128. );
  129. }
  130. await AuthenticatorDB.instance.clearTable();
  131. _key = null;
  132. _cachedToken = null;
  133. _secretKey = null;
  134. _authSecretKey = null;
  135. Bus.instance.fire(SignedOutEvent());
  136. }
  137. Future<KeyGenResult> generateKey(String password) async {
  138. // Create a master key
  139. final masterKey = CryptoUtil.generateKey();
  140. // Create a recovery key
  141. final recoveryKey = CryptoUtil.generateKey();
  142. // Encrypt master key and recovery key with each other
  143. final encryptedMasterKey = CryptoUtil.encryptSync(masterKey, recoveryKey);
  144. final encryptedRecoveryKey = CryptoUtil.encryptSync(recoveryKey, masterKey);
  145. // Derive a key from the password that will be used to encrypt and
  146. // decrypt the master key
  147. final kekSalt = CryptoUtil.getSaltToDeriveKey();
  148. final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
  149. utf8.encode(password),
  150. kekSalt,
  151. );
  152. final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
  153. // Encrypt the key with this derived key
  154. final encryptedKeyData =
  155. CryptoUtil.encryptSync(masterKey, derivedKeyResult.key);
  156. // Generate a public-private keypair and encrypt the latter
  157. final keyPair = CryptoUtil.generateKeyPair();
  158. final encryptedSecretKeyData =
  159. CryptoUtil.encryptSync(keyPair.secretKey.extractBytes(), masterKey);
  160. final attributes = KeyAttributes(
  161. CryptoUtil.bin2base64(kekSalt),
  162. CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
  163. CryptoUtil.bin2base64(encryptedKeyData.nonce!),
  164. CryptoUtil.bin2base64(keyPair.publicKey),
  165. CryptoUtil.bin2base64(encryptedSecretKeyData.encryptedData!),
  166. CryptoUtil.bin2base64(encryptedSecretKeyData.nonce!),
  167. derivedKeyResult.memLimit,
  168. derivedKeyResult.opsLimit,
  169. CryptoUtil.bin2base64(encryptedMasterKey.encryptedData!),
  170. CryptoUtil.bin2base64(encryptedMasterKey.nonce!),
  171. CryptoUtil.bin2base64(encryptedRecoveryKey.encryptedData!),
  172. CryptoUtil.bin2base64(encryptedRecoveryKey.nonce!),
  173. );
  174. final privateAttributes = PrivateKeyAttributes(
  175. CryptoUtil.bin2base64(masterKey),
  176. CryptoUtil.bin2hex(recoveryKey),
  177. CryptoUtil.bin2base64(keyPair.secretKey.extractBytes()),
  178. );
  179. return KeyGenResult(attributes, privateAttributes, loginKey);
  180. }
  181. Future<Tuple2<KeyAttributes, Uint8List>> getAttributesForNewPassword(
  182. String password,
  183. ) async {
  184. // Get master key
  185. final masterKey = getKey();
  186. // Derive a key from the password that will be used to encrypt and
  187. // decrypt the master key
  188. final kekSalt = CryptoUtil.getSaltToDeriveKey();
  189. final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
  190. utf8.encode(password),
  191. kekSalt,
  192. );
  193. final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
  194. // Encrypt the key with this derived key
  195. final encryptedKeyData =
  196. CryptoUtil.encryptSync(masterKey!, derivedKeyResult.key);
  197. final existingAttributes = getKeyAttributes();
  198. final updatedAttributes = existingAttributes!.copyWith(
  199. kekSalt: CryptoUtil.bin2base64(kekSalt),
  200. encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
  201. keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!),
  202. memLimit: derivedKeyResult.memLimit,
  203. opsLimit: derivedKeyResult.opsLimit,
  204. );
  205. return Tuple2(updatedAttributes, loginKey);
  206. }
  207. // decryptSecretsAndGetLoginKey decrypts the master key and recovery key
  208. // with the given password and save them in local secure storage.
  209. // This method also returns the keyEncKey that can be used for performing
  210. // SRP setup for existing users.
  211. Future<Uint8List> decryptSecretsAndGetKeyEncKey(
  212. String password,
  213. KeyAttributes attributes, {
  214. Uint8List? keyEncryptionKey,
  215. }) async {
  216. _logger.info('Start decryptAndSaveSecrets');
  217. keyEncryptionKey ??= await CryptoUtil.deriveKey(
  218. utf8.encode(password),
  219. CryptoUtil.base642bin(attributes.kekSalt),
  220. attributes.memLimit,
  221. attributes.opsLimit,
  222. );
  223. _logger.info('user-key done');
  224. Uint8List key;
  225. try {
  226. key = CryptoUtil.decryptSync(
  227. CryptoUtil.base642bin(attributes.encryptedKey),
  228. keyEncryptionKey,
  229. CryptoUtil.base642bin(attributes.keyDecryptionNonce),
  230. );
  231. } catch (e) {
  232. _logger.severe('master-key failed, incorrect password?', e);
  233. throw Exception("Incorrect password");
  234. }
  235. _logger.info("master-key done");
  236. await setKey(CryptoUtil.bin2base64(key));
  237. final secretKey = CryptoUtil.decryptSync(
  238. CryptoUtil.base642bin(attributes.encryptedSecretKey),
  239. key,
  240. CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
  241. );
  242. _logger.info("secret-key done");
  243. await setSecretKey(CryptoUtil.bin2base64(secretKey));
  244. final token = CryptoUtil.openSealSync(
  245. CryptoUtil.base642bin(getEncryptedToken()!),
  246. CryptoUtil.base642bin(attributes.publicKey),
  247. secretKey,
  248. );
  249. _logger.info('appToken done');
  250. await setToken(
  251. CryptoUtil.bin2base64(token, urlSafe: true),
  252. );
  253. return keyEncryptionKey;
  254. }
  255. Future<void> recover(String recoveryKey) async {
  256. // check if user has entered mnemonic code
  257. if (recoveryKey.contains(' ')) {
  258. if (recoveryKey.split(' ').length != mnemonicKeyWordCount) {
  259. throw AssertionError(
  260. 'recovery code should have $mnemonicKeyWordCount words',
  261. );
  262. }
  263. recoveryKey = bip39.mnemonicToEntropy(recoveryKey);
  264. }
  265. final attributes = getKeyAttributes();
  266. Uint8List masterKey;
  267. try {
  268. masterKey = await CryptoUtil.decrypt(
  269. CryptoUtil.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
  270. CryptoUtil.hex2bin(recoveryKey),
  271. CryptoUtil.base642bin(attributes.masterKeyDecryptionNonce),
  272. );
  273. } catch (e) {
  274. _logger.severe(e);
  275. rethrow;
  276. }
  277. await setKey(CryptoUtil.bin2base64(masterKey));
  278. final secretKey = CryptoUtil.decryptSync(
  279. CryptoUtil.base642bin(attributes.encryptedSecretKey),
  280. masterKey,
  281. CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
  282. );
  283. await setSecretKey(CryptoUtil.bin2base64(secretKey));
  284. final token = CryptoUtil.openSealSync(
  285. CryptoUtil.base642bin(getEncryptedToken()!),
  286. CryptoUtil.base642bin(attributes.publicKey),
  287. secretKey,
  288. );
  289. await setToken(
  290. CryptoUtil.bin2base64(token, urlSafe: true),
  291. );
  292. }
  293. String getHttpEndpoint() {
  294. return _preferences.getString(endPointKey) ?? endpoint;
  295. }
  296. Future<void> setHttpEndpoint(String endpoint) async {
  297. await _preferences.setString(endPointKey, endpoint);
  298. Bus.instance.fire(EndpointUpdatedEvent());
  299. }
  300. String? getToken() {
  301. _cachedToken ??= _preferences.getString(tokenKey);
  302. return _cachedToken;
  303. }
  304. bool isLoggedIn() {
  305. return getToken() != null;
  306. }
  307. Future<void> setToken(String token) async {
  308. _cachedToken = token;
  309. await _preferences.setString(tokenKey, token);
  310. Bus.instance.fire(SignedInEvent());
  311. }
  312. Future<void> setEncryptedToken(String encryptedToken) async {
  313. await _preferences.setString(encryptedTokenKey, encryptedToken);
  314. }
  315. String? getEncryptedToken() {
  316. return _preferences.getString(encryptedTokenKey);
  317. }
  318. String? getEmail() {
  319. return _preferences.getString(emailKey);
  320. }
  321. Future<void> setEmail(String email) async {
  322. await _preferences.setString(emailKey, email);
  323. }
  324. int? getUserID() {
  325. return _preferences.getInt(userIDKey);
  326. }
  327. Future<void> setUserID(int userID) async {
  328. await _preferences.setInt(userIDKey, userID);
  329. }
  330. Future<void> setKeyAttributes(KeyAttributes attributes) async {
  331. await _preferences.setString(keyAttributesKey, attributes.toJson());
  332. }
  333. KeyAttributes? getKeyAttributes() {
  334. final jsonValue = _preferences.getString(keyAttributesKey);
  335. if (jsonValue == null) {
  336. return null;
  337. } else {
  338. return KeyAttributes.fromJson(jsonValue);
  339. }
  340. }
  341. Future<void> setKey(String key) async {
  342. _key = key;
  343. await _secureStorage.write(
  344. key: keyKey,
  345. value: key,
  346. iOptions: _secureStorageOptionsIOS,
  347. );
  348. }
  349. Future<void> setSecretKey(String? secretKey) async {
  350. _secretKey = secretKey;
  351. await _secureStorage.write(
  352. key: secretKeyKey,
  353. value: secretKey,
  354. iOptions: _secureStorageOptionsIOS,
  355. );
  356. }
  357. Future<void> setAuthSecretKey(String? authSecretKey) async {
  358. _authSecretKey = authSecretKey;
  359. await _secureStorage.write(
  360. key: authSecretKeyKey,
  361. value: authSecretKey,
  362. iOptions: _secureStorageOptionsIOS,
  363. );
  364. }
  365. Uint8List? getKey() {
  366. return _key == null ? null : CryptoUtil.base642bin(_key!);
  367. }
  368. Uint8List? getSecretKey() {
  369. return _secretKey == null ? null : CryptoUtil.base642bin(_secretKey!);
  370. }
  371. Uint8List? getAuthSecretKey() {
  372. return _authSecretKey == null
  373. ? null
  374. : CryptoUtil.base642bin(_authSecretKey!);
  375. }
  376. Uint8List? getOfflineSecretKey() {
  377. return _offlineAuthKey == null
  378. ? null
  379. : CryptoUtil.base642bin(_offlineAuthKey!);
  380. }
  381. Uint8List getRecoveryKey() {
  382. final keyAttributes = getKeyAttributes()!;
  383. return CryptoUtil.decryptSync(
  384. CryptoUtil.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
  385. getKey()!,
  386. CryptoUtil.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
  387. );
  388. }
  389. // Caution: This directory is cleared on app start
  390. String getTempDirectory() {
  391. return _tempDirectory;
  392. }
  393. bool hasConfiguredAccount() {
  394. return getToken() != null && _key != null;
  395. }
  396. bool hasOptedForOfflineMode() {
  397. return _preferences.getBool(hasOptedForOfflineModeKey) ?? false;
  398. }
  399. Future<void> optForOfflineMode() async {
  400. if ((await _secureStorage.containsKey(
  401. key: offlineAuthSecretKey,
  402. iOptions: _secureStorageOptionsIOS,
  403. ))) {
  404. _offlineAuthKey = await _secureStorage.read(
  405. key: offlineAuthSecretKey,
  406. iOptions: _secureStorageOptionsIOS,
  407. );
  408. } else {
  409. _offlineAuthKey = CryptoUtil.bin2base64(CryptoUtil.generateKey());
  410. await _secureStorage.write(
  411. key: offlineAuthSecretKey,
  412. value: _offlineAuthKey,
  413. iOptions: _secureStorageOptionsIOS,
  414. );
  415. }
  416. await _preferences.setBool(hasOptedForOfflineModeKey, true);
  417. }
  418. bool shouldShowLockScreen() {
  419. if (_preferences.containsKey(keyShouldShowLockScreen)) {
  420. return _preferences.getBool(keyShouldShowLockScreen)!;
  421. } else {
  422. return false;
  423. }
  424. }
  425. Future<void> setShouldShowLockScreen(bool value) {
  426. return _preferences.setBool(keyShouldShowLockScreen, value);
  427. }
  428. void setVolatilePassword(String volatilePassword) {
  429. _volatilePassword = volatilePassword;
  430. }
  431. void resetVolatilePassword() {
  432. _volatilePassword = null;
  433. }
  434. String? getVolatilePassword() {
  435. return _volatilePassword;
  436. }
  437. }