From 815d4d5a35470bb3fa1e95d886c5ce7641f52021 Mon Sep 17 00:00:00 2001 From: Krille Fear Date: Tue, 16 Nov 2021 15:44:09 +0100 Subject: [PATCH] Revert "feat: Use sembast over sqflite" This reverts commit 2fbf7376f6b464c60efcb3e8448c0beac7131638. --- lib/utils/client_manager.dart | 7 +- .../matrix_sdk_extensions.dart/codec.dart | 120 ---------------- .../flutter_matrix_sembast_database.dart | 101 +++++++++++--- .../flutter_matrix_sembast_database_old.dart | 131 ------------------ pubspec.lock | 23 +-- pubspec.yaml | 2 - 6 files changed, 85 insertions(+), 299 deletions(-) delete mode 100644 lib/utils/matrix_sdk_extensions.dart/codec.dart delete mode 100644 lib/utils/matrix_sdk_extensions.dart/flutter_matrix_sembast_database_old.dart diff --git a/lib/utils/client_manager.dart b/lib/utils/client_manager.dart index 0715c722..279fc0cb 100644 --- a/lib/utils/client_manager.dart +++ b/lib/utils/client_manager.dart @@ -5,13 +5,13 @@ import 'package:flutter/foundation.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:matrix/encryption/utils/key_verification.dart'; import 'package:matrix/matrix.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:sembast/sembast.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'famedlysdk_store.dart'; +import 'matrix_sdk_extensions.dart/flutter_matrix_hive_database.dart'; import 'matrix_sdk_extensions.dart/flutter_matrix_sembast_database.dart'; -import 'matrix_sdk_extensions.dart/flutter_matrix_sembast_database_old.dart'; abstract class ClientManager { static const String clientNamespace = 'im.fluffychat.store.clients'; @@ -81,8 +81,7 @@ abstract class ClientManager { }, importantStateEvents: {'im.ponies.room_emotes'}, databaseBuilder: FlutterMatrixSembastDatabase.databaseBuilder, - legacyDatabaseBuilder: FlutterMatrixSembastDatabaseOld.databaseBuilder, - //legacyDatabaseBuilder: FlutterMatrixHiveStore.hiveDatabaseBuilder, + legacyDatabaseBuilder: FlutterMatrixHiveStore.hiveDatabaseBuilder, supportedLoginTypes: { AuthenticationTypes.password, if (PlatformInfos.isMobile || PlatformInfos.isWeb) diff --git a/lib/utils/matrix_sdk_extensions.dart/codec.dart b/lib/utils/matrix_sdk_extensions.dart/codec.dart deleted file mode 100644 index 8f93b223..00000000 --- a/lib/utils/matrix_sdk_extensions.dart/codec.dart +++ /dev/null @@ -1,120 +0,0 @@ -//@dart=2.12 - -import 'dart:convert'; -import 'dart:math'; -import 'dart:typed_data'; - -import 'package:crypto/crypto.dart'; -import 'package:encrypt/encrypt.dart'; -import 'package:sembast/sembast.dart'; - -var _random = Random.secure(); - -/// Random bytes generator -Uint8List _randBytes(int length) { - return Uint8List.fromList( - List.generate(length, (i) => _random.nextInt(256))); -} - -/// Generate an encryption password based on a user input password -/// -/// It uses MD5 which generates a 16 bytes blob, size needed for Salsa20 -Uint8List _generateEncryptPassword(String password) { - final blob = Uint8List.fromList(md5.convert(utf8.encode(password)).bytes); - assert(blob.length == 16); - return blob; -} - -/// Salsa20 based encoder -class _EncryptEncoder extends Converter { - final Salsa20 salsa20; - - _EncryptEncoder(this.salsa20); - - @override - String convert(dynamic input) { - // Generate random initial value - final iv = _randBytes(8); - final ivEncoded = base64.encode(iv); - assert(ivEncoded.length == 12); - - // Encode the input value - final encoded = - Encrypter(salsa20).encrypt(json.encode(input), iv: IV(iv)).base64; - - // Prepend the initial value - return '$ivEncoded$encoded'; - } -} - -/// Salsa20 based decoder -class _EncryptDecoder extends Converter { - final Salsa20 salsa20; - - _EncryptDecoder(this.salsa20); - - @override - dynamic convert(String input) { - // Read the initial value that was prepended - assert(input.length >= 12); - final iv = base64.decode(input.substring(0, 12)); - - // Extract the real input - input = input.substring(12); - - // Decode the input - final decoded = - json.decode(Encrypter(salsa20).decrypt64(input, iv: IV(iv))); - if (decoded is Map) { - return decoded.cast(); - } - return decoded; - } -} - -/// Salsa20 based Codec -class _EncryptCodec extends Codec { - late _EncryptEncoder _encoder; - late _EncryptDecoder _decoder; - - _EncryptCodec(Uint8List passwordBytes) { - final salsa20 = Salsa20(Key(passwordBytes)); - _encoder = _EncryptEncoder(salsa20); - _decoder = _EncryptDecoder(salsa20); - } - - @override - Converter get decoder => _decoder; - - @override - Converter get encoder => _encoder; -} - -/// Our plain text signature -const _encryptCodecSignature = 'encrypt'; - -/// Create a codec to use to open a database with encrypted stored data. -/// -/// Hash (md5) of the password is used (but never stored) as a key to encrypt -/// the data using the Salsa20 algorithm with a random (8 bytes) initial value -/// -/// This is just used as a demonstration and should not be considered as a -/// reference since its implementation (and storage format) might change. -/// -/// No performance metrics has been made to check whether this is a viable -/// solution for big databases. -/// -/// The usage is then -/// -/// ```dart -/// // Initialize the encryption codec with a user password -/// var codec = getEncryptSembastCodec(password: '[your_user_password]'); -/// // Open the database with the codec -/// Database db = await factory.openDatabase(dbPath, codec: codec); -/// -/// // ...your database is ready to use -/// ``` -SembastCodec getEncryptSembastCodec({required String password}) => SembastCodec( - signature: _encryptCodecSignature, - codec: _EncryptCodec(_generateEncryptPassword(password)), - ); diff --git a/lib/utils/matrix_sdk_extensions.dart/flutter_matrix_sembast_database.dart b/lib/utils/matrix_sdk_extensions.dart/flutter_matrix_sembast_database.dart index 7307d5c7..c3953f5c 100644 --- a/lib/utils/matrix_sdk_extensions.dart/flutter_matrix_sembast_database.dart +++ b/lib/utils/matrix_sdk_extensions.dart/flutter_matrix_sembast_database.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; @@ -10,13 +11,9 @@ import 'package:matrix/matrix.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sembast/sembast.dart'; import 'package:sembast/sembast_io.dart'; -import 'package:sembast_sqflite/sembast_sqflite.dart'; import 'package:sembast_web/sembast_web.dart'; -import 'package:sqflite/sqflite.dart' as sqflite; -import 'package:sqflite_common_ffi/sqflite_ffi.dart' as sqflite_ffi; import '../platform_infos.dart'; -import 'codec.dart'; class FlutterMatrixSembastDatabase extends MatrixSembastDatabase { FlutterMatrixSembastDatabase( @@ -32,7 +29,7 @@ class FlutterMatrixSembastDatabase extends MatrixSembastDatabase { ); static const String _cipherStorageKey = 'sembast_encryption_key'; - static const int _cipherStorageKeyLength = 1024; + static const int _cipherStorageKeyLength = 512; static Future databaseBuilder( Client client) async { @@ -56,6 +53,7 @@ class FlutterMatrixSembastDatabase extends MatrixSembastDatabase { // workaround for if we just wrote to the key and it still doesn't exist final rawEncryptionKey = await secureStorage.read(key: _cipherStorageKey); if (rawEncryptionKey == null) throw MissingPluginException(); + codec = getEncryptSembastCodec(password: rawEncryptionKey); } on MissingPluginException catch (_) { Logs().i('Sembast encryption is not supported on this platform'); @@ -65,26 +63,13 @@ class FlutterMatrixSembastDatabase extends MatrixSembastDatabase { client.clientName, codec: codec, path: await _findDatabasePath(client), - dbFactory: kIsWeb - ? databaseFactoryWeb - : getDatabaseFactorySqflite(sqflite.databaseFactory), + dbFactory: kIsWeb ? databaseFactoryWeb : databaseFactoryIo, ); await db.open(); Logs().d('Sembast is ready'); return db; } - static DatabaseFactory get factory { - if (kIsWeb) return databaseFactoryWeb; - if (Platform.isAndroid || Platform.isIOS) { - return getDatabaseFactorySqflite(sqflite.databaseFactory); - } - if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) { - return getDatabaseFactorySqflite(sqflite_ffi.databaseFactoryFfi); - } - return databaseFactoryIo; - } - static Future _findDatabasePath(Client client) async { String path = client.clientName; if (!kIsWeb) { @@ -98,7 +83,7 @@ class FlutterMatrixSembastDatabase extends MatrixSembastDatabase { directory = Directory.current; } } - path = '${directory.path}${client.clientName}.sqflite'; + path = '${directory.path}${client.clientName}.db'; } return path; } @@ -144,3 +129,79 @@ class FlutterMatrixSembastDatabase extends MatrixSembastDatabase { return; } } + +class _EncryptEncoder extends Converter, String> { + final String key; + final String signature; + _EncryptEncoder(this.key, this.signature); + + @override + String convert(Map input) { + String encoded; + switch (signature) { + case "Salsa20": + encoded = Encrypter(Salsa20(Key.fromUtf8(key))) + .encrypt(json.encode(input), iv: IV.fromLength(8)) + .base64; + break; + case "AES": + encoded = Encrypter(AES(Key.fromUtf8(key))) + .encrypt(json.encode(input), iv: IV.fromLength(16)) + .base64; + break; + default: + throw FormatException('invalid $signature'); + break; + } + return encoded; + } +} + +class _EncryptDecoder extends Converter> { + final String key; + final String signature; + _EncryptDecoder(this.key, this.signature); + + @override + Map convert(String input) { + dynamic decoded; + switch (signature) { + case "Salsa20": + decoded = json.decode(Encrypter(Salsa20(Key.fromUtf8(key))) + .decrypt64(input, iv: IV.fromLength(8))); + break; + case "AES": + decoded = json.decode(Encrypter(AES(Key.fromUtf8(key))) + .decrypt64(input, iv: IV.fromLength(16))); + break; + default: + break; + } + if (decoded is Map) { + return decoded.cast(); + } + throw FormatException('invalid input $input'); + } +} + +class _EncryptCodec extends Codec, String> { + final String signature; + _EncryptEncoder _encoder; + _EncryptDecoder _decoder; + _EncryptCodec(String password, this.signature) { + _encoder = _EncryptEncoder(password, signature); + _decoder = _EncryptDecoder(password, signature); + } + + @override + Converter> get decoder => _decoder; + + @override + Converter, String> get encoder => _encoder; +} + +// Salsa20 (16 length key required) or AES (32 length key required) +SembastCodec getEncryptSembastCodec( + {@required String password, String signature = "Salsa20"}) => + SembastCodec( + signature: signature, codec: _EncryptCodec(password, signature)); diff --git a/lib/utils/matrix_sdk_extensions.dart/flutter_matrix_sembast_database_old.dart b/lib/utils/matrix_sdk_extensions.dart/flutter_matrix_sembast_database_old.dart deleted file mode 100644 index 31e09289..00000000 --- a/lib/utils/matrix_sdk_extensions.dart/flutter_matrix_sembast_database_old.dart +++ /dev/null @@ -1,131 +0,0 @@ -import 'dart:io'; -import 'dart:typed_data'; - -import 'package:flutter/foundation.dart' hide Key; -import 'package:flutter/services.dart'; - -import 'package:encrypt/encrypt.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:matrix/matrix.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:sembast/sembast.dart'; -import 'package:sembast/sembast_io.dart'; -import 'package:sembast_web/sembast_web.dart'; - -import '../platform_infos.dart'; -import 'codec.dart'; - -class FlutterMatrixSembastDatabaseOld extends MatrixSembastDatabase { - FlutterMatrixSembastDatabaseOld( - String name, { - SembastCodec codec, - String path, - DatabaseFactory dbFactory, - }) : super( - name, - codec: codec, - path: path, - dbFactory: dbFactory, - ); - - static const String _cipherStorageKey = 'sembast_encryption_key'; - static const int _cipherStorageKeyLength = 512; - - static Future databaseBuilder( - Client client) async { - Logs().d('Open Sembast...'); - SembastCodec codec; - try { - // Workaround for secure storage is calling Platform.operatingSystem on web - if (kIsWeb) throw MissingPluginException(); - - const secureStorage = FlutterSecureStorage(); - final containsEncryptionKey = - await secureStorage.containsKey(key: _cipherStorageKey); - if (!containsEncryptionKey) { - final key = SecureRandom(_cipherStorageKeyLength).base64; - await secureStorage.write( - key: _cipherStorageKey, - value: key, - ); - } - - // workaround for if we just wrote to the key and it still doesn't exist - final rawEncryptionKey = await secureStorage.read(key: _cipherStorageKey); - if (rawEncryptionKey == null) throw MissingPluginException(); - - codec = getEncryptSembastCodec(password: rawEncryptionKey); - } on MissingPluginException catch (_) { - Logs().i('Sembast encryption is not supported on this platform'); - } - - final db = FlutterMatrixSembastDatabaseOld( - client.clientName, - codec: codec, - path: await _findDatabasePath(client), - dbFactory: kIsWeb ? databaseFactoryWeb : databaseFactoryIo, - ); - await db.open(); - Logs().d('Sembast is ready'); - return db; - } - - static Future _findDatabasePath(Client client) async { - String path = client.clientName; - if (!kIsWeb) { - Directory directory; - try { - directory = await getApplicationSupportDirectory(); - } catch (_) { - try { - directory = await getLibraryDirectory(); - } catch (_) { - directory = Directory.current; - } - } - path = '${directory.path}${client.clientName}.db'; - } - return path; - } - - @override - int get maxFileSize => supportsFileStoring ? 100 * 1024 * 1024 : 0; - @override - bool get supportsFileStoring => (PlatformInfos.isIOS || - PlatformInfos.isAndroid || - PlatformInfos.isDesktop); - - Future _getFileStoreDirectory() async { - try { - try { - return (await getApplicationSupportDirectory()).path; - } catch (_) { - return (await getApplicationDocumentsDirectory()).path; - } - } catch (_) { - return (await getDownloadsDirectory()).path; - } - } - - @override - Future getFile(Uri mxcUri) async { - if (!supportsFileStoring) return null; - final tempDirectory = await _getFileStoreDirectory(); - final file = - File('$tempDirectory/${Uri.encodeComponent(mxcUri.toString())}'); - if (await file.exists() == false) return null; - final bytes = await file.readAsBytes(); - return bytes; - } - - @override - Future storeFile(Uri mxcUri, Uint8List bytes, int time) async { - if (!supportsFileStoring) return null; - final tempDirectory = await _getFileStoreDirectory(); - final file = - File('$tempDirectory/${Uri.encodeComponent(mxcUri.toString())}'); - if (await file.exists()) return; - await file.writeAsBytes(bytes); - return; - } -} diff --git a/pubspec.lock b/pubspec.lock index 023fef59..0567662e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1171,13 +1171,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.1" - sembast_sqflite: - dependency: "direct main" - description: - name: sembast_sqflite - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0+1" sembast_web: dependency: "direct main" description: @@ -1315,21 +1308,7 @@ packages: name: sqflite_common url: "https://pub.dartlang.org" source: hosted - version: "2.0.1+1" - sqflite_common_ffi: - dependency: "direct main" - description: - name: sqflite_common_ffi - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - sqlite3: - dependency: transitive - description: - name: sqlite3 - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "2.0.0+2" stack_trace: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 137b5eea..59bd7d1e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,12 +63,10 @@ dependencies: record: ^3.0.0 salomon_bottom_bar: ^3.1.0 scroll_to_index: ^2.1.0 - sembast_sqflite: ^2.0.0+1 sembast_web: ^2.0.1+1 sentry: ^6.0.1 share: ^2.0.4 slugify: ^2.0.0 - sqflite_common_ffi: ^2.1.0 swipe_to_action: ^0.2.0 uni_links: ^0.5.1 unifiedpush: ^1.0.6