Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Plan B][Re-login] Upgrade flutter_secure_storage to access the key in Keychain iOS #857

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
platform :ios, '11.0'
platform :ios, '11.3'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
Expand Down Expand Up @@ -49,7 +49,7 @@ post_install do |installer|
config.build_settings['ENABLE_BITCODE'] = 'NO'

# see https://github.com/flutter-webrtc/flutter-webrtc/issues/1054
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.3'
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = 'arm64 i386'

config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
Expand Down
6 changes: 3 additions & 3 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.3;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down Expand Up @@ -757,7 +757,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.3;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
Expand Down Expand Up @@ -807,7 +807,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.3;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down
18 changes: 9 additions & 9 deletions lib/data/hive/hive_collection_tom_database.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import 'dart:convert';
import 'dart:io';

import 'package:fluffychat/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/twake_secure_storage.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive/hive.dart';
import 'package:matrix/matrix.dart';
import 'package:path_provider/path_provider.dart';
Expand All @@ -31,11 +32,10 @@ class HiveCollectionToMDatabase {
throw MissingPluginException();
}

const secureStorage = FlutterSecureStorage();
final containsEncryptionKey = await secureStorage.read(
key: FlutterHiveCollectionsDatabase.cipherStorageKey,
) !=
null;
final secureStorage = TwakeSecureStorage();
final containsEncryptionKey = await secureStorage.containsEncryptionKey(
FlutterHiveCollectionsDatabase.cipherStorageKey,
);
if (!containsEncryptionKey) {
// do not try to create a buggy secure storage for new Linux users
if (Platform.isLinux) throw MissingPluginException();
Expand All @@ -54,12 +54,12 @@ class HiveCollectionToMDatabase {

hiverCipher = HiveAesCipher(base64Url.decode(rawEncryptionKey));
} on MissingPluginException catch (_) {
const FlutterSecureStorage()
TwakeSecureStorage()
.delete(key: FlutterHiveCollectionsDatabase.cipherStorageKey)
.catchError((_) {});
Logs().i('Hive encryption is not supported on this platform');
} catch (e, s) {
const FlutterSecureStorage()
TwakeSecureStorage()
.delete(key: FlutterHiveCollectionsDatabase.cipherStorageKey)
.catchError((_) {});
Logs().w('Unable to init Hive encryption', e, s);
Expand All @@ -74,7 +74,7 @@ class HiveCollectionToMDatabase {
await db.open();
} catch (e, s) {
Logs().w('Unable to open ToM Hive.', e, s);
const FlutterSecureStorage()
TwakeSecureStorage()
.delete(key: FlutterHiveCollectionsDatabase.cipherStorageKey);
await db.clear().catchError((_) {});
await Hive.deleteFromDisk();
Expand Down
8 changes: 4 additions & 4 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/di/global/get_it_initializer.dart';
import 'package:fluffychat/utils/client_manager.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/twake_secure_storage.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_lock/flutter_app_lock.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import 'package:media_kit/media_kit.dart';

import 'utils/background_push.dart';
import 'widgets/twake_app.dart';
import 'widgets/lock_screen.dart';
import 'widgets/twake_app.dart';

void main() async {
// Our background push shared isolate accesses flutter-internal things very early in the startup proccess
Expand Down Expand Up @@ -60,8 +61,7 @@ Future<void> startGui(List<Client> clients) async {
String? pin;
if (PlatformInfos.isMobile) {
try {
pin =
await const FlutterSecureStorage().read(key: SettingKeys.appLockKey);
pin = await TwakeSecureStorage().read(key: SettingKeys.appLockKey);
} catch (e, s) {
Logs().d('Unable to read PIN from Secure storage', e, s);
}
Expand Down
6 changes: 3 additions & 3 deletions lib/pages/bootstrap/bootstrap_dialog.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/bootstrap/tom_bootstrap_dialog.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/twake_secure_storage.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/adaptive_flat_button.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:matrix/encryption.dart';
Expand Down Expand Up @@ -90,7 +90,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
_recoveryKeyStored = false;
bootstrap =
widget.client.encryption!.bootstrap(onUpdate: (_) => setState(() {}));
final key = await const FlutterSecureStorage().read(key: _secureStorageKey);
final key = await TwakeSecureStorage().read(key: _secureStorageKey);
if (key == null) return;
_recoveryKeyTextEditingController.text = key;
}
Expand Down Expand Up @@ -188,7 +188,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
(_recoveryKeyCopied || _storeInSecureStorage == true)
? () {
if (_storeInSecureStorage == true) {
const FlutterSecureStorage().write(
TwakeSecureStorage().write(
key: _secureStorageKey,
value: key,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import 'dart:convert';
import 'dart:typed_data';

import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/pages/bootstrap/bootstrap_dialog.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/twake_secure_storage.dart';
import 'package:fluffychat/utils/twake_snackbar.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';

import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flutter_app_lock/flutter_app_lock.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:intl/intl.dart';
import 'package:matrix/matrix.dart';

import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'settings_security_view.dart';

class SettingsSecurity extends StatefulWidget {
Expand Down Expand Up @@ -62,7 +61,7 @@ class SettingsSecurityController extends State<SettingsSecurity> {

void setAppLockAction() async {
final currentLock =
await const FlutterSecureStorage().read(key: SettingKeys.appLockKey);
await TwakeSecureStorage().read(key: SettingKeys.appLockKey);
if (currentLock?.isNotEmpty ?? false) {
await AppLock.of(context)!.showLockScreen();
}
Expand All @@ -89,7 +88,7 @@ class SettingsSecurityController extends State<SettingsSecurity> {
],
);
if (newLock != null) {
await const FlutterSecureStorage()
await TwakeSecureStorage()
.write(key: SettingKeys.appLockKey, value: newLock.single);
if (newLock.single.isEmpty) {
AppLock.of(context)!.disable();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:io';

import 'package:fluffychat/utils/matrix_sdk_extensions/twake_secure_storage.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flutter/foundation.dart' hide Key;
import 'package:flutter/services.dart';
Expand Down Expand Up @@ -34,14 +35,14 @@ class FlutterHiveCollectionsDatabase extends HiveCollectionsDatabase {
throw MissingPluginException();
}

const secureStorage = FlutterSecureStorage();
final secureStorage = TwakeSecureStorage();
final containsEncryptionKey =
await secureStorage.read(key: cipherStorageKey) != null;
await secureStorage.containsEncryptionKey(cipherStorageKey);
if (!containsEncryptionKey) {
// do not try to create a buggy secure storage for new Linux users
if (Platform.isLinux) throw MissingPluginException();
final key = Hive.generateSecureKey();
await secureStorage.write(
await secureStorage.writeEncryptionKey(
key: cipherStorageKey,
value: base64UrlEncode(key),
);
Expand All @@ -58,8 +59,7 @@ class FlutterHiveCollectionsDatabase extends HiveCollectionsDatabase {
.catchError((_) {});
Logs().i('Hive encryption is not supported on this platform');
} catch (e, s) {
const FlutterSecureStorage()
.delete(key: cipherStorageKey)
TwakeSecureStorage().delete(key: cipherStorageKey)
.catchError((_) {});
Logs().w('Unable to init Hive encryption', e, s);
}
Expand All @@ -74,7 +74,7 @@ class FlutterHiveCollectionsDatabase extends HiveCollectionsDatabase {
await db.open();
} catch (e, s) {
Logs().w('Unable to open Hive. Delete database and storage key...', e, s);
const FlutterSecureStorage().delete(key: cipherStorageKey);
TwakeSecureStorage().delete(key: cipherStorageKey);
await db
.clear(supportDeleteCollections: !PlatformInfos.isWeb)
.catchError((_) {});
Expand Down
148 changes: 148 additions & 0 deletions lib/utils/matrix_sdk_extensions/twake_secure_storage.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import 'dart:async';

import 'package:fluffychat/utils/famedlysdk_store.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flutter/services.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:matrix/matrix.dart';

class TwakeSecureStorage {
final String _iOSDuplicatedKeyExceptionCode = '-25299';
final String _databaseBuiltKey = 'db_built_key';

TwakeSecureStorage._();

static final TwakeSecureStorage _instance = TwakeSecureStorage._();

factory TwakeSecureStorage() => _instance;

Store? _store;

Store get store => _store ??= Store();

final FlutterSecureStorage _flutterSecureStorage = const FlutterSecureStorage(
iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
);

Future<void> markDatabaseBuilt() {
return store.setItemBool(_databaseBuiltKey, true);
}

Future<void> markDatabaseNotBuilt() {
return store.setItemBool(_databaseBuiltKey, false);
}

Future<bool> isDatabaseBuilt() async {
return await store.getItemBool(_databaseBuiltKey, false);
}

Future<void> deleteEncryptionKey({
required String key,
}) async {
await markDatabaseNotBuilt();
await _flutterSecureStorage.delete(key: key);
}

Future<void> writeEncryptionKey({
required String key,
required String value,
}) async {
try {
await _flutterSecureStorage.write(key: key, value: value);
await markDatabaseBuilt();
} on PlatformException catch (e, s) {
Logs().e('TwakeSecureStorage::writeEncryptionKey() $e $s');
if (PlatformInfos.isIOS) {
if (_isDuplicatedIOSKeyException(e)) {
await delete(key: key);
await _flutterSecureStorage.write(key: key, value: value);
await markDatabaseBuilt();
}
}
rethrow;
}
}

Future<bool> containsEncryptionKey(String key) async {
final dbBuilt = await isDatabaseBuilt();
if (dbBuilt) {
Logs()
.i('TwakeSecureStorage::containsEncryptionKey() database was built');
return await _platformContainsEncryptionKey(key);
} else {
return false;
}
}

Future<bool> _platformContainsEncryptionKey(String key) async {
if (PlatformInfos.isIOS) {
final isAvailable =
await _flutterSecureStorage.isCupertinoProtectedDataAvailable();
if (isAvailable) {
Logs().i(
'TwakeSecureStorage::_platformContainsEncryptionKey() Cupertino protected data is available');
final value = await _flutterSecureStorage.read(key: key);
return value != null;
}

Logs().i(
'TwakeSecureStorage::_platformContainsEncryptionKey() Cupertino protected data is not available');
final completer = Completer<String?>();
late StreamSubscription<bool> subscription;
subscription = _flutterSecureStorage
.onCupertinoProtectedDataAvailabilityChanged
.listen((protectedDataAvailable) {
Logs().i(
'TwakeSecureStorage::_platformContainsEncryptionKey() onCupertinoProtectedDataAvailabilityChanged: $protectedDataAvailable',
);
if (protectedDataAvailable) {
completer.complete(_flutterSecureStorage.read(key: key));
subscription.cancel();
}
});
final value = await completer.future;
Logs().wtf('onCupertinoProtectedDataAvailabilityChanged: done');
return value != null;
} else {
final value = await _flutterSecureStorage.read(key: key);
return value != null;
}
}

Future<String?> read({
required String key,
}) async {
return _flutterSecureStorage.read(key: key);
}

Future<void> write({
required String key,
required String? value,
}) async {
try {
await _flutterSecureStorage.write(key: key, value: value);
} on PlatformException catch (e, s) {
Logs().e('TwakeSecureStorage::write() $e $s');
if (PlatformInfos.isIOS) {
if (_isDuplicatedIOSKeyException(e)) {
await delete(key: key);
await _flutterSecureStorage.write(key: key, value: value);
}
}
rethrow;
}
}

Future<void> delete({
required String key,
}) async {
await _flutterSecureStorage.delete(key: key);
}

bool _isDuplicatedIOSKeyException(PlatformException platformException) {
if (platformException.code == _iOSDuplicatedKeyExceptionCode) {
return true;
}
return false;
}
}
Loading