From 8e6fd6bc7c203014baf950031c3b7543e323b142 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 23 Sep 2022 11:32:38 +0300 Subject: [PATCH 01/13] Implement Configuration encryption key --- lib/src/configuration.dart | 31 +++++++++++----- lib/src/native/realm_core.dart | 4 ++- test/configuration_test.dart | 26 ++++++++++++-- test/realm_test.dart | 65 ++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 11 deletions(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index bdb28c9f1..df58633ae 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -57,6 +57,9 @@ typedef MigrationCallback = void Function(Migration migration, int oldSchemaVers /// Configuration used to create a [Realm] instance /// {@category Configuration} abstract class Configuration implements Finalizable { + + static const int encryptionKeySize = 64; + /// The default realm filename to be used. static String get defaultRealmName => _path.basename(defaultRealmPath); static set defaultRealmName(String name) => defaultRealmPath = _path.join(_path.dirname(defaultRealmPath), _path.basename(name)); @@ -90,7 +93,11 @@ abstract class Configuration implements Finalizable { this.schemaObjects, { String? path, this.fifoFilesFallbackPath, + this.encryptionKey, }) { + if (encryptionKey != null && encryptionKey!.length != Configuration.encryptionKeySize) { + throw RealmException("EncryptionKey must be 64 bytes"); + } this.path = path ?? _path.join(_path.dirname(_defaultPath), _path.basename(defaultRealmName)); } @@ -112,12 +119,11 @@ abstract class Configuration implements Finalizable { /// If omitted the [defaultPath] for the platform will be used. late final String path; - //TODO: Config: Support encryption keys. https://github.com/realm/realm-dart/issues/88 - // /// The key used to encrypt the entire [Realm]. - // /// - // /// A full 64byte (512bit) key for AES-256 encryption. - // /// Once set, must be specified each time the file is used. - // final List? encryptionKey; + /// The key used to encrypt the entire [Realm]. + /// + /// A full 64byte (512bit) key for AES-256 encryption. + /// Once set, must be specified each time the file is used. + final List? encryptionKey; /// Constructs a [LocalConfiguration] static LocalConfiguration local( @@ -126,10 +132,11 @@ abstract class Configuration implements Finalizable { int schemaVersion = 0, String? fifoFilesFallbackPath, String? path, + List? encryptionKey, bool disableFormatUpgrade = false, bool isReadOnly = false, ShouldCompactCallback? shouldCompactCallback, - MigrationCallback? migrationCallback, + MigrationCallback? migrationCallback }) => LocalConfiguration._( schemaObjects, @@ -137,10 +144,11 @@ abstract class Configuration implements Finalizable { schemaVersion: schemaVersion, fifoFilesFallbackPath: fifoFilesFallbackPath, path: path, + encryptionKey: encryptionKey, disableFormatUpgrade: disableFormatUpgrade, isReadOnly: isReadOnly, shouldCompactCallback: shouldCompactCallback, - migrationCallback: migrationCallback, + migrationCallback: migrationCallback ); /// Constructs a [InMemoryConfiguration] @@ -161,6 +169,7 @@ abstract class Configuration implements Finalizable { List schemaObjects, { String? fifoFilesFallbackPath, String? path, + List? encryptionKey, SyncErrorHandler syncErrorHandler = defaultSyncErrorHandler, SyncClientResetErrorHandler syncClientResetErrorHandler = const ManualSyncClientResetHandler(_defaultSyncClientResetHandler), }) => @@ -169,6 +178,7 @@ abstract class Configuration implements Finalizable { schemaObjects, fifoFilesFallbackPath: fifoFilesFallbackPath, path: path, + encryptionKey: encryptionKey, syncErrorHandler: syncErrorHandler, syncClientResetErrorHandler: syncClientResetErrorHandler, ); @@ -178,11 +188,13 @@ abstract class Configuration implements Finalizable { List schemaObjects, { String? fifoFilesFallbackPath, String? path, + List? encryptionKey, }) => DisconnectedSyncConfiguration._( schemaObjects, fifoFilesFallbackPath: fifoFilesFallbackPath, path: path, + encryptionKey: encryptionKey, ); } @@ -196,6 +208,7 @@ class LocalConfiguration extends Configuration { this.schemaVersion = 0, super.fifoFilesFallbackPath, super.path, + super.encryptionKey, this.disableFormatUpgrade = false, this.isReadOnly = false, this.shouldCompactCallback, @@ -285,6 +298,7 @@ class FlexibleSyncConfiguration extends Configuration { super.schemaObjects, { super.fifoFilesFallbackPath, super.path, + super.encryptionKey, this.syncErrorHandler = defaultSyncErrorHandler, this.syncClientResetErrorHandler = const ManualSyncClientResetHandler(_defaultSyncClientResetHandler), }) : super._(); @@ -313,6 +327,7 @@ class DisconnectedSyncConfiguration extends Configuration { super.schemaObjects, { super.fifoFilesFallbackPath, super.path, + super.encryptionKey, }) : super._(); } diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 123e0dd2e..f1c1e23df 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -225,7 +225,9 @@ class _RealmCore { } else if (config is DisconnectedSyncConfiguration) { _realmLib.realm_config_set_force_sync_history(configPtr, true); } - + if (config.encryptionKey != null && config is! InMemoryConfiguration) { + _realmLib.realm_config_set_encryption_key(configPtr, config.encryptionKey!.toUint8Ptr(arena), Configuration.encryptionKeySize); + } return configHandle; }); } diff --git a/test/configuration_test.dart b/test/configuration_test.dart index 0a67b7b46..fb868a62a 100644 --- a/test/configuration_test.dart +++ b/test/configuration_test.dart @@ -506,7 +506,7 @@ Future main([List? args]) async { path.basename('my-custom-realm-name.realm'), ); final config = Configuration.flexibleSync(user, [Event.schema], path: customPath); - var realm = Realm(config); + var realm = getRealm(config); }); baasTest('Configuration.disconnectedSync', (appConfig) async { @@ -527,7 +527,29 @@ Future main([List? args]) async { realm.close(); final disconnectedSyncConfig = Configuration.disconnectedSync(schema, path: realmPath); - final disconnectedRealm = Realm(disconnectedSyncConfig); + final disconnectedRealm = getRealm(disconnectedSyncConfig); expect(disconnectedRealm.find(oid), isNotNull); }); + + test('Configuration set short encryption key', () { + List key = [1, 2, 3]; + expect(() => Configuration.local([Car.schema], encryptionKey: key), throws("EncryptionKey must be 64 bytes")); + }); + + test('Configuration set encryption key', () { + List key = List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); + Configuration.local([Car.schema], encryptionKey: key); + }); + + baasTest('FlexibleSyncConfiguration set long encryption key', (appConfiguration) async { + final app = App(appConfiguration); + final credentials = Credentials.anonymous(); + final user = await app.logIn(credentials); + + List key = List.generate(Configuration.encryptionKeySize + 10, (i) => random.nextInt(256)); + expect( + () => Configuration.flexibleSync(user, [Task.schema], encryptionKey: key), + throws("EncryptionKey must be 64 bytes"), + ); + }); } diff --git a/test/realm_test.dart b/test/realm_test.dart index c8848ece7..b31d3e5e7 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -824,6 +824,71 @@ Future main([List? args]) async { expect(stored.location, now.location); expect(stored.location.name, 'Europe/Copenhagen'); }); + + test('Realm open with wrong encryption key', () { + List key = List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); + final config = Configuration.local([Car.schema], encryptionKey: key); + final realm = getRealm(config); + expect( + () => getRealm(Configuration.local([Car.schema])), + throws("already opened with a different encryption key"), + ); + }); + + test('Realm - open local encrypted realm with empty encryption key', () { + List key = List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); + final config = Configuration.local([Car.schema], encryptionKey: key); + final realm = getRealm(config); + expect( + () => getRealm(Configuration.local([Car.schema])), + throws("already opened with a different encryption key"), + ); + }); + + test('Realm - open local not encrypted realm with encryption key', () { + List key = List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); + final config = Configuration.local([Car.schema]); + final realm = getRealm(config); + expect( + () => getRealm(Configuration.local([Car.schema], encryptionKey: key)), + throws("already opened with a different encryption key"), + ); + }); + + test('Realm - open local encrypted realm wrong encryption key', () { + List key = List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); + final config = Configuration.local([Car.schema], encryptionKey: key); + final realm = getRealm(config); + + List key1 = List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); + expect( + () => getRealm(Configuration.local([Car.schema], encryptionKey: key1)), + throws("already opened with a different encryption key"), + ); + }); + + test('Realm - open local encrypted realm with encryption key', () { + List key = List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); + final config = Configuration.local([Car.schema], encryptionKey: key); + final realm = getRealm(config); + final realm1 = getRealm(config); + expect(realm1.isClosed, false); + }); + + baasTest('Realm - open remote encrypted realm with encryption key', (appConfiguration) async { + final app = App(appConfiguration); + final credentials = Credentials.anonymous(); + final user = await app.logIn(credentials); + List key = List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); + final configuration = Configuration.flexibleSync(user, [Task.schema], encryptionKey: key); + + final realm = getRealm(configuration); + expect(realm.isClosed, false); + expect( + () => getRealm(Configuration.flexibleSync(user, [Task.schema])), + throws("already opened with a different encryption key"), + ); + }); } extension on When { From f55f42d25ed4d5a405e86d52c3a8ee27b399c6a5 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 23 Sep 2022 11:36:58 +0300 Subject: [PATCH 02/13] formatting dart code --- lib/src/configuration.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index df58633ae..c95c9a5ca 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -132,11 +132,11 @@ abstract class Configuration implements Finalizable { int schemaVersion = 0, String? fifoFilesFallbackPath, String? path, - List? encryptionKey, + List? encryptionKey, bool disableFormatUpgrade = false, bool isReadOnly = false, ShouldCompactCallback? shouldCompactCallback, - MigrationCallback? migrationCallback + MigrationCallback? migrationCallback, }) => LocalConfiguration._( schemaObjects, From 4eeaa3a415a13605080135e6f9a3f00c5bbb1409 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 23 Sep 2022 11:45:00 +0300 Subject: [PATCH 03/13] Update Changelog and API doc --- CHANGELOG.md | 1 + lib/src/configuration.dart | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dbf520bf..383504007 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * You can now set a realm property of type `T` to any object `o` where `o is T`. Previously it was required that `o.runtimeType == T`. ([#904](https://github.com/realm/realm-dart/issues/904)) * Performance of indexOf on realm lists has been improved. It now uses realm-core instead of the generic version from ListMixin. ([#911](https://github.com/realm/realm-dart/pull/911)). * Added support for migrations for local Realms. You can now construct a configuration with a migration callback that will be invoked if the schema version of the file on disk is lower than the schema version supplied by the callback. (Issue [#70](https://github.com/realm/realm-dart/issues/70)) +* Added support for `encryptionKey` to [Configuration.local], [Configuration.flexibleSync] and [Configuration.disconnectedSync] so realm files can be encrypted and existing encrypted files from other Realm sources opened (assuming you have the key)([#920](https://github.com/realm/realm-dart/pull/920)) A minimal example looks like this: ```dart diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index c95c9a5ca..e2ddac5a4 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -123,6 +123,7 @@ abstract class Configuration implements Finalizable { /// /// A full 64byte (512bit) key for AES-256 encryption. /// Once set, must be specified each time the file is used. + /// If null encryption is not enabled. final List? encryptionKey; /// Constructs a [LocalConfiguration] From 7ec19af0f464781b8daa194e45628c58230986f4 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 23 Sep 2022 11:56:09 +0300 Subject: [PATCH 04/13] Update Changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 383504007..af3d98863 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ * You can now set a realm property of type `T` to any object `o` where `o is T`. Previously it was required that `o.runtimeType == T`. ([#904](https://github.com/realm/realm-dart/issues/904)) * Performance of indexOf on realm lists has been improved. It now uses realm-core instead of the generic version from ListMixin. ([#911](https://github.com/realm/realm-dart/pull/911)). * Added support for migrations for local Realms. You can now construct a configuration with a migration callback that will be invoked if the schema version of the file on disk is lower than the schema version supplied by the callback. (Issue [#70](https://github.com/realm/realm-dart/issues/70)) -* Added support for `encryptionKey` to [Configuration.local], [Configuration.flexibleSync] and [Configuration.disconnectedSync] so realm files can be encrypted and existing encrypted files from other Realm sources opened (assuming you have the key)([#920](https://github.com/realm/realm-dart/pull/920)) +* Added support for `encryptionKey` to `Configuration.local`, `Configuration.flexibleSync` and `Configuration.disconnectedSync` so realm files can be encrypted and existing encrypted files from other Realm sources opened (assuming you have the key)([#920](https://github.com/realm/realm-dart/pull/920)) A minimal example looks like this: ```dart From 17c60928bafcf817cf9d379f6c4f275fc4bd022a Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 23 Sep 2022 16:20:26 +0300 Subject: [PATCH 05/13] Code review changes --- lib/src/native/realm_core.dart | 3 +- test/configuration_test.dart | 2 +- test/realm_test.dart | 71 +++++++++++++++------------------- 3 files changed, 35 insertions(+), 41 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index f1c1e23df..c982e1e42 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -225,7 +225,8 @@ class _RealmCore { } else if (config is DisconnectedSyncConfiguration) { _realmLib.realm_config_set_force_sync_history(configPtr, true); } - if (config.encryptionKey != null && config is! InMemoryConfiguration) { + if (config.encryptionKey != null) { + assert(config is! InMemoryConfiguration, "Encryption keys are not allowed for InMemoryConfiguration"); _realmLib.realm_config_set_encryption_key(configPtr, config.encryptionKey!.toUint8Ptr(arena), Configuration.encryptionKeySize); } return configHandle; diff --git a/test/configuration_test.dart b/test/configuration_test.dart index fb868a62a..0a0f66ae8 100644 --- a/test/configuration_test.dart +++ b/test/configuration_test.dart @@ -536,7 +536,7 @@ Future main([List? args]) async { expect(() => Configuration.local([Car.schema], encryptionKey: key), throws("EncryptionKey must be 64 bytes")); }); - test('Configuration set encryption key', () { + test('Configuration set a correct encryption key', () { List key = List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); Configuration.local([Car.schema], encryptionKey: key); }); diff --git a/test/realm_test.dart b/test/realm_test.dart index b31d3e5e7..dfb8c1528 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -825,54 +825,26 @@ Future main([List? args]) async { expect(stored.location.name, 'Europe/Copenhagen'); }); - test('Realm open with wrong encryption key', () { - List key = List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); - final config = Configuration.local([Car.schema], encryptionKey: key); - final realm = getRealm(config); - expect( - () => getRealm(Configuration.local([Car.schema])), - throws("already opened with a different encryption key"), - ); + test('Realm - open local not encrypted realm with encryption key', () { + openEncryptedRealm(null, generateValidKey()); }); test('Realm - open local encrypted realm with empty encryption key', () { - List key = List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); - final config = Configuration.local([Car.schema], encryptionKey: key); - final realm = getRealm(config); - expect( - () => getRealm(Configuration.local([Car.schema])), - throws("already opened with a different encryption key"), - ); + openEncryptedRealm(generateValidKey(), null); }); - test('Realm - open local not encrypted realm with encryption key', () { - List key = List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); - final config = Configuration.local([Car.schema]); - final realm = getRealm(config); - expect( - () => getRealm(Configuration.local([Car.schema], encryptionKey: key)), - throws("already opened with a different encryption key"), - ); + test('Realm - open local encrypted realm with wrong encryption key', () { + openEncryptedRealm(generateValidKey(), generateValidKey()); }); - test('Realm - open local encrypted realm wrong encryption key', () { - List key = List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); - final config = Configuration.local([Car.schema], encryptionKey: key); - final realm = getRealm(config); - - List key1 = List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); - expect( - () => getRealm(Configuration.local([Car.schema], encryptionKey: key1)), - throws("already opened with a different encryption key"), - ); + test('Realm - open local encrypted realm with the correct encryption key', () { + List key = generateValidKey(); + openEncryptedRealm(key, key); }); - test('Realm - open local encrypted realm with encryption key', () { - List key = List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); - final config = Configuration.local([Car.schema], encryptionKey: key); - final realm = getRealm(config); - final realm1 = getRealm(config); - expect(realm1.isClosed, false); + test('Realm - open closed local encrypted realm with the correct encryption key', () { + List key = generateValidKey(); + openEncryptedRealm(key, key, afterEncrypt: (realm) => realm.close()); }); baasTest('Realm - open remote encrypted realm with encryption key', (appConfiguration) async { @@ -891,6 +863,27 @@ Future main([List? args]) async { }); } +List generateValidKey() { + return List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); +} + +void openEncryptedRealm(List? encryptionKey, List? decryptionKey, {void Function(Realm)? afterEncrypt}) { + final config1 = Configuration.local([Car.schema], encryptionKey: encryptionKey); + final config2 = Configuration.local([Car.schema], encryptionKey: decryptionKey); + final realm = getRealm(config1); + if (afterEncrypt != null) { + afterEncrypt(realm); + } + if (encryptionKey == decryptionKey) { + final decriptedRealm = getRealm(config2); + } else { + expect( + () => getRealm(config2), + throws("already opened with a different encryption key"), + ); + } +} + extension on When { tz.TZDateTime get dateTime => tz.TZDateTime.from(dateTimeUtc, tz.getLocation(locationName)); } From 38d581ad3101337464c36eda4bf82ee56bbc408d Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 23 Sep 2022 16:28:45 +0300 Subject: [PATCH 06/13] Open closed realm with an invalid encryption key --- test/realm_test.dart | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index dfb8c1528..3f2f932e5 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -829,11 +829,11 @@ Future main([List? args]) async { openEncryptedRealm(null, generateValidKey()); }); - test('Realm - open local encrypted realm with empty encryption key', () { + test('Realm - open local encrypted realm with an empty encryption key', () { openEncryptedRealm(generateValidKey(), null); }); - test('Realm - open local encrypted realm with wrong encryption key', () { + test('Realm - open local encrypted realm with an invalid encryption key', () { openEncryptedRealm(generateValidKey(), generateValidKey()); }); @@ -847,6 +847,10 @@ Future main([List? args]) async { openEncryptedRealm(key, key, afterEncrypt: (realm) => realm.close()); }); + test('Realm - open closed local encrypted realm with an invalid encryption key', () { + openEncryptedRealm(generateValidKey(), generateValidKey(), afterEncrypt: (realm) => realm.close()); + }); + baasTest('Realm - open remote encrypted realm with encryption key', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); @@ -879,7 +883,7 @@ void openEncryptedRealm(List? encryptionKey, List? decryptionKey, {voi } else { expect( () => getRealm(config2), - throws("already opened with a different encryption key"), + throws(realm.isClosed ? "Realm file decryption failed" : "already opened with a different encryption key"), ); } } From 5ed2ecd59ec8068c4572a1d54cebe1a2712821c7 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 23 Sep 2022 16:30:05 +0300 Subject: [PATCH 07/13] Formatting --- lib/src/configuration.dart | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index e2ddac5a4..95bbd03d6 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -57,7 +57,6 @@ typedef MigrationCallback = void Function(Migration migration, int oldSchemaVers /// Configuration used to create a [Realm] instance /// {@category Configuration} abstract class Configuration implements Finalizable { - static const int encryptionKeySize = 64; /// The default realm filename to be used. @@ -139,18 +138,16 @@ abstract class Configuration implements Finalizable { ShouldCompactCallback? shouldCompactCallback, MigrationCallback? migrationCallback, }) => - LocalConfiguration._( - schemaObjects, - initialDataCallback: initialDataCallback, - schemaVersion: schemaVersion, - fifoFilesFallbackPath: fifoFilesFallbackPath, - path: path, + LocalConfiguration._(schemaObjects, + initialDataCallback: initialDataCallback, + schemaVersion: schemaVersion, + fifoFilesFallbackPath: fifoFilesFallbackPath, + path: path, encryptionKey: encryptionKey, - disableFormatUpgrade: disableFormatUpgrade, - isReadOnly: isReadOnly, - shouldCompactCallback: shouldCompactCallback, - migrationCallback: migrationCallback - ); + disableFormatUpgrade: disableFormatUpgrade, + isReadOnly: isReadOnly, + shouldCompactCallback: shouldCompactCallback, + migrationCallback: migrationCallback); /// Constructs a [InMemoryConfiguration] static InMemoryConfiguration inMemory( From 47828d42fd77633d68d214387a85ce18157be64a Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Mon, 26 Sep 2022 11:55:39 +0300 Subject: [PATCH 08/13] Remove encryptionKeySize value from Configuration --- lib/src/configuration.dart | 4 +--- lib/src/native/realm_core.dart | 4 +++- test/configuration_test.dart | 4 ++-- test/realm_test.dart | 4 ++-- test/test.dart | 1 + 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 95bbd03d6..2775c2206 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -57,8 +57,6 @@ typedef MigrationCallback = void Function(Migration migration, int oldSchemaVers /// Configuration used to create a [Realm] instance /// {@category Configuration} abstract class Configuration implements Finalizable { - static const int encryptionKeySize = 64; - /// The default realm filename to be used. static String get defaultRealmName => _path.basename(defaultRealmPath); static set defaultRealmName(String name) => defaultRealmPath = _path.join(_path.dirname(defaultRealmPath), _path.basename(name)); @@ -94,7 +92,7 @@ abstract class Configuration implements Finalizable { this.fifoFilesFallbackPath, this.encryptionKey, }) { - if (encryptionKey != null && encryptionKey!.length != Configuration.encryptionKeySize) { + if (encryptionKey != null && encryptionKey!.length != realmCore.encryptionKeySize) { throw RealmException("EncryptionKey must be 64 bytes"); } this.path = path ?? _path.join(_path.dirname(_defaultPath), _path.basename(defaultRealmName)); diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index c982e1e42..dfe0d1c9a 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -56,6 +56,8 @@ class _RealmCore { // ignore: unused_field static const int RLM_INVALID_OBJECT_KEY = -1; + final int encryptionKeySize = 64; + static Object noopUserdata = Object(); // Hide the RealmCore class and make it a singleton @@ -227,7 +229,7 @@ class _RealmCore { } if (config.encryptionKey != null) { assert(config is! InMemoryConfiguration, "Encryption keys are not allowed for InMemoryConfiguration"); - _realmLib.realm_config_set_encryption_key(configPtr, config.encryptionKey!.toUint8Ptr(arena), Configuration.encryptionKeySize); + _realmLib.realm_config_set_encryption_key(configPtr, config.encryptionKey!.toUint8Ptr(arena), encryptionKeySize); } return configHandle; }); diff --git a/test/configuration_test.dart b/test/configuration_test.dart index 0a0f66ae8..36cd63d58 100644 --- a/test/configuration_test.dart +++ b/test/configuration_test.dart @@ -537,7 +537,7 @@ Future main([List? args]) async { }); test('Configuration set a correct encryption key', () { - List key = List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); + List key = List.generate(encryptionKeySize, (i) => random.nextInt(256)); Configuration.local([Car.schema], encryptionKey: key); }); @@ -546,7 +546,7 @@ Future main([List? args]) async { final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); - List key = List.generate(Configuration.encryptionKeySize + 10, (i) => random.nextInt(256)); + List key = List.generate(encryptionKeySize + 10, (i) => random.nextInt(256)); expect( () => Configuration.flexibleSync(user, [Task.schema], encryptionKey: key), throws("EncryptionKey must be 64 bytes"), diff --git a/test/realm_test.dart b/test/realm_test.dart index 3f2f932e5..16fe1d5a0 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -855,7 +855,7 @@ Future main([List? args]) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); - List key = List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); + List key = List.generate(encryptionKeySize, (i) => random.nextInt(256)); final configuration = Configuration.flexibleSync(user, [Task.schema], encryptionKey: key); final realm = getRealm(configuration); @@ -868,7 +868,7 @@ Future main([List? args]) async { } List generateValidKey() { - return List.generate(Configuration.encryptionKeySize, (i) => random.nextInt(256)); + return List.generate(encryptionKeySize, (i) => random.nextInt(256)); } void openEncryptedRealm(List? encryptionKey, List? decryptionKey, {void Function(Realm)? afterEncrypt}) { diff --git a/test/test.dart b/test/test.dart index 402c6856a..87835a0b3 100644 --- a/test/test.dart +++ b/test/test.dart @@ -221,6 +221,7 @@ O8BM8KOSx9wGyoGs4+OusvRkJizhPaIwa3FInLs4r+xZW9Bp6RndsmVECtvXRv5d 87ztpg6o3DZJRmTp2lAnkNLmxXlFkOSNIwiT3qqyRZOh4DuxPOpfg9K+vtFmRdEJ RwIDAQAB -----END PUBLIC KEY-----'''; +final int encryptionKeySize = 64; enum AppNames { flexible, From 0d65d9114a798ce512520d1094e6ea3e3fa13d0d Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 30 Sep 2022 16:01:25 +0300 Subject: [PATCH 09/13] Added tests for byte exceeding keys --- lib/src/configuration.dart | 5 +++-- test/configuration_test.dart | 9 +++++++-- test/realm_test.dart | 6 ++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 2775c2206..ddb890e94 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -92,9 +92,10 @@ abstract class Configuration implements Finalizable { this.fifoFilesFallbackPath, this.encryptionKey, }) { - if (encryptionKey != null && encryptionKey!.length != realmCore.encryptionKeySize) { - throw RealmException("EncryptionKey must be 64 bytes"); + if (encryptionKey != null && encryptionKey!.isNotEmpty && encryptionKey?.length != realmCore.encryptionKeySize) { + throw RealmException("Wrong encryption key size (must be 0 or ${realmCore.encryptionKeySize})"); } + this.path = path ?? _path.join(_path.dirname(_defaultPath), _path.basename(defaultRealmName)); } diff --git a/test/configuration_test.dart b/test/configuration_test.dart index 36cd63d58..5f51717e4 100644 --- a/test/configuration_test.dart +++ b/test/configuration_test.dart @@ -533,7 +533,12 @@ Future main([List? args]) async { test('Configuration set short encryption key', () { List key = [1, 2, 3]; - expect(() => Configuration.local([Car.schema], encryptionKey: key), throws("EncryptionKey must be 64 bytes")); + expect(() => Configuration.local([Car.schema], encryptionKey: key), throws("Wrong encryption key size (must be 0 or $encryptionKeySize)")); + }); + + test('Configuration set byte exceeding encryption key', () { + List byteExceedingKey = List.generate(encryptionKeySize, (i) => random.nextInt(4294967296)); + Configuration.local([Car.schema], encryptionKey: byteExceedingKey); }); test('Configuration set a correct encryption key', () { @@ -549,7 +554,7 @@ Future main([List? args]) async { List key = List.generate(encryptionKeySize + 10, (i) => random.nextInt(256)); expect( () => Configuration.flexibleSync(user, [Task.schema], encryptionKey: key), - throws("EncryptionKey must be 64 bytes"), + throws("Wrong encryption key size (must be 0 or $encryptionKeySize)"), ); }); } diff --git a/test/realm_test.dart b/test/realm_test.dart index 16fe1d5a0..7aa357e41 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -851,6 +851,11 @@ Future main([List? args]) async { openEncryptedRealm(generateValidKey(), generateValidKey(), afterEncrypt: (realm) => realm.close()); }); + test('Realm - open local realm with key with values exceeding byte size 255', () { + List byteExceedingKey = List.generate(encryptionKeySize, (i) => random.nextInt(4294967296)); + openEncryptedRealm(byteExceedingKey, byteExceedingKey); + }); + baasTest('Realm - open remote encrypted realm with encryption key', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); @@ -880,6 +885,7 @@ void openEncryptedRealm(List? encryptionKey, List? decryptionKey, {voi } if (encryptionKey == decryptionKey) { final decriptedRealm = getRealm(config2); + expect(decriptedRealm.isClosed, false); } else { expect( () => getRealm(config2), From fe9185fc66f71335d5d7114dfa0977449e7b45a5 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 30 Sep 2022 16:06:30 +0300 Subject: [PATCH 10/13] Close Realm before open again --- test/realm_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index 7aa357e41..a37e3923d 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -853,7 +853,7 @@ Future main([List? args]) async { test('Realm - open local realm with key with values exceeding byte size 255', () { List byteExceedingKey = List.generate(encryptionKeySize, (i) => random.nextInt(4294967296)); - openEncryptedRealm(byteExceedingKey, byteExceedingKey); + openEncryptedRealm(byteExceedingKey, byteExceedingKey, afterEncrypt: (realm) => realm.close()); }); baasTest('Realm - open remote encrypted realm with encryption key', (appConfiguration) async { From faff2ffe087cb0f2a6ea7886a178eec15430bd35 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 30 Sep 2022 17:04:04 +0300 Subject: [PATCH 11/13] EncryptionKey must be 64 bytes --- lib/src/configuration.dart | 4 ++-- test/configuration_test.dart | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index ddb890e94..565d5633a 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -92,8 +92,8 @@ abstract class Configuration implements Finalizable { this.fifoFilesFallbackPath, this.encryptionKey, }) { - if (encryptionKey != null && encryptionKey!.isNotEmpty && encryptionKey?.length != realmCore.encryptionKeySize) { - throw RealmException("Wrong encryption key size (must be 0 or ${realmCore.encryptionKeySize})"); + if (encryptionKey != null && encryptionKey?.length != realmCore.encryptionKeySize) { + throw RealmException("EncryptionKey must be ${realmCore.encryptionKeySize} bytes."); } this.path = path ?? _path.join(_path.dirname(_defaultPath), _path.basename(defaultRealmName)); diff --git a/test/configuration_test.dart b/test/configuration_test.dart index 5f51717e4..7b5cd9a90 100644 --- a/test/configuration_test.dart +++ b/test/configuration_test.dart @@ -533,7 +533,10 @@ Future main([List? args]) async { test('Configuration set short encryption key', () { List key = [1, 2, 3]; - expect(() => Configuration.local([Car.schema], encryptionKey: key), throws("Wrong encryption key size (must be 0 or $encryptionKeySize)")); + expect( + () => Configuration.local([Car.schema], encryptionKey: key), + throws("EncryptionKey must be $encryptionKeySize bytes."), + ); }); test('Configuration set byte exceeding encryption key', () { @@ -554,7 +557,7 @@ Future main([List? args]) async { List key = List.generate(encryptionKeySize + 10, (i) => random.nextInt(256)); expect( () => Configuration.flexibleSync(user, [Task.schema], encryptionKey: key), - throws("Wrong encryption key size (must be 0 or $encryptionKeySize)"), + throws("EncryptionKey must be $encryptionKeySize bytes."), ); }); } From 62a4440a347719ad507e5b3c1b3b99a79f95e5ff Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 30 Sep 2022 18:39:43 +0300 Subject: [PATCH 12/13] Bytes validation --- lib/src/configuration.dart | 21 +++++++++++++++++---- test/configuration_test.dart | 9 ++++++--- test/realm_test.dart | 5 ----- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 565d5633a..9ef964f1d 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -92,10 +92,7 @@ abstract class Configuration implements Finalizable { this.fifoFilesFallbackPath, this.encryptionKey, }) { - if (encryptionKey != null && encryptionKey?.length != realmCore.encryptionKeySize) { - throw RealmException("EncryptionKey must be ${realmCore.encryptionKeySize} bytes."); - } - + _validateEncryptionKey(encryptionKey); this.path = path ?? _path.join(_path.dirname(_defaultPath), _path.basename(defaultRealmName)); } @@ -193,6 +190,22 @@ abstract class Configuration implements Finalizable { path: path, encryptionKey: encryptionKey, ); + + void _validateEncryptionKey(List? key) { + if (key == null) { + return; + } + + if (key.length != realmCore.encryptionKeySize) { + throw RealmException("Wrong encryption key size (must be ${realmCore.encryptionKeySize}, but was ${key.length})"); + } + + int notAByteElement = key.firstWhere((e) => e > 255, orElse: () => -1); + if (notAByteElement >= 0) { + throw RealmException('''Encryption key must be a list of bytes with allowed values form 0 to 255. + Invalid value $notAByteElement found at index ${key.indexOf(notAByteElement)}.'''); + } + } } /// [LocalConfiguration] is used to open local [Realm] instances, diff --git a/test/configuration_test.dart b/test/configuration_test.dart index 7b5cd9a90..53eb02206 100644 --- a/test/configuration_test.dart +++ b/test/configuration_test.dart @@ -535,13 +535,16 @@ Future main([List? args]) async { List key = [1, 2, 3]; expect( () => Configuration.local([Car.schema], encryptionKey: key), - throws("EncryptionKey must be $encryptionKeySize bytes."), + throws("Wrong encryption key size"), ); }); test('Configuration set byte exceeding encryption key', () { List byteExceedingKey = List.generate(encryptionKeySize, (i) => random.nextInt(4294967296)); - Configuration.local([Car.schema], encryptionKey: byteExceedingKey); + expect( + () => Configuration.local([Car.schema], encryptionKey: byteExceedingKey), + throws("Encryption key must be a list of bytes with allowed values form 0 to 255"), + ); }); test('Configuration set a correct encryption key', () { @@ -557,7 +560,7 @@ Future main([List? args]) async { List key = List.generate(encryptionKeySize + 10, (i) => random.nextInt(256)); expect( () => Configuration.flexibleSync(user, [Task.schema], encryptionKey: key), - throws("EncryptionKey must be $encryptionKeySize bytes."), + throws("Wrong encryption key size"), ); }); } diff --git a/test/realm_test.dart b/test/realm_test.dart index a37e3923d..c8934ba51 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -851,11 +851,6 @@ Future main([List? args]) async { openEncryptedRealm(generateValidKey(), generateValidKey(), afterEncrypt: (realm) => realm.close()); }); - test('Realm - open local realm with key with values exceeding byte size 255', () { - List byteExceedingKey = List.generate(encryptionKeySize, (i) => random.nextInt(4294967296)); - openEncryptedRealm(byteExceedingKey, byteExceedingKey, afterEncrypt: (realm) => realm.close()); - }); - baasTest('Realm - open remote encrypted realm with encryption key', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); From 7b16c62fae4baf8b257531db6f1ed36093fa7309 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Mon, 10 Oct 2022 13:01:37 +0300 Subject: [PATCH 13/13] Remove assert --- lib/src/native/realm_core.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 16d77b4fa..6c0d49ea0 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -228,7 +228,6 @@ class _RealmCore { _realmLib.realm_config_set_force_sync_history(configPtr, true); } if (config.encryptionKey != null) { - assert(config is! InMemoryConfiguration, "Encryption keys are not allowed for InMemoryConfiguration"); _realmLib.realm_config_set_encryption_key(configPtr, config.encryptionKey!.toUint8Ptr(arena), encryptionKeySize); } return configHandle;