diff --git a/.github/workflows/at_client_sdk.yaml b/.github/workflows/at_client_sdk.yaml index 2629343d0..0facd5ff8 100644 --- a/.github/workflows/at_client_sdk.yaml +++ b/.github/workflows/at_client_sdk.yaml @@ -30,7 +30,7 @@ jobs: with: sdk: ${{ matrix.dart-channel}} - - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 + - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 with: go-version: 'stable' cache-dependency-path: tools/osv-scanner/go.sum diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index b9e391883..9bf5d8c89 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -59,7 +59,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: SARIF file path: results.sarif @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@babb554ede22fd5605947329c4d04d8e7a0b8155 # v3.27.7 + uses: github/codeql-action/upload-sarif@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 with: sarif_file: results.sarif diff --git a/packages/at_client/CHANGELOG.md b/packages/at_client/CHANGELOG.md index e69195c26..7e8022078 100644 --- a/packages/at_client/CHANGELOG.md +++ b/packages/at_client/CHANGELOG.md @@ -1,8 +1,11 @@ ## 3.4.0 -- feat: Allows clients to skip delete commits until a specific commitID +- feat: Allows clients to skip delete commits until a specific commitID during initial sync +## 3.3.1 +- fix: isInSync bug fix for apkam connection +- fix: remove deprecated isPaginated param from SyncVerbBuilder in SyncServiceImpl - build[deps]: Upgraded dependencies for the following packages: - - at_commons to v5.1.1 - - at_persistence_secondary_server to v3.1.0 + - at_commons to v5.1.2 +- feat: Introduce "publicKeyHash" which uses SHA hashing to verify change in the encryption public key ## 3.3.0 - feat: add the AtClientBindings mixin which was initially added to the noports_core package but has broader applicability. diff --git a/packages/at_client/lib/src/decryption_service/shared_key_decryption.dart b/packages/at_client/lib/src/decryption_service/shared_key_decryption.dart index 0e372ea94..db222dc3c 100644 --- a/packages/at_client/lib/src/decryption_service/shared_key_decryption.dart +++ b/packages/at_client/lib/src/decryption_service/shared_key_decryption.dart @@ -1,3 +1,4 @@ +import 'package:at_chops/at_chops.dart'; import 'package:at_client/src/client/at_client_spec.dart'; import 'package:at_client/src/decryption_service/decryption.dart'; import 'package:at_client/src/response/default_response_parser.dart'; @@ -5,7 +6,6 @@ import 'package:at_client/src/util/encryption_util.dart'; import 'package:at_commons/at_builders.dart'; import 'package:at_commons/at_commons.dart'; import 'package:at_utils/at_logger.dart'; -import 'package:at_chops/at_chops.dart'; /// Class responsible for decrypting the value of shared key's that are not owned /// by currentAtSign @@ -50,14 +50,26 @@ class SharedKeyDecryption implements AtKeyDecryption { intent: Intent.fetchEncryptionPublicKey, exceptionScenario: ExceptionScenario.localVerbExecutionFailed); } - if (currentAtSignPublicKey != null && - (atKey.metadata.pubKeyCS != null && - atKey.metadata.pubKeyCS != - EncryptionUtil.md5CheckSum(currentAtSignPublicKey))) { + if (currentAtSignPublicKey.isNullOrEmpty) { + throw AtPublicKeyNotFoundException('Public key cannot be null or empty'); + } + + final isPubKeyHashMismatch = atKey.metadata.pubKeyHash != null && + atKey.metadata.pubKeyHash?.hash != + AtChops.hashWith(HashingAlgoType.fromString( + atKey.metadata.pubKeyHash!.hashingAlgo)) + .hash(currentAtSignPublicKey!.codeUnits); + + final isPubKeyCSMismatch = atKey.metadata.pubKeyCS != null && + atKey.metadata.pubKeyCS != + EncryptionUtil.md5CheckSum(currentAtSignPublicKey!); + + if (isPubKeyHashMismatch || isPubKeyCSMismatch) { throw AtPublicKeyChangeException( - 'Public key has changed. Cannot decrypt shared key ${atKey.toString()}', - intent: Intent.fetchEncryptionPublicKey, - exceptionScenario: ExceptionScenario.decryptionFailed); + 'Public key has changed. Cannot decrypt shared key ${atKey.toString()}', + intent: Intent.fetchEncryptionPublicKey, + exceptionScenario: ExceptionScenario.decryptionFailed, + ); } AtEncryptionResult decryptionResultFromAtChops; diff --git a/packages/at_client/lib/src/encryption_service/abstract_atkey_encryption.dart b/packages/at_client/lib/src/encryption_service/abstract_atkey_encryption.dart index 6158b2827..bb7b52eb1 100644 --- a/packages/at_client/lib/src/encryption_service/abstract_atkey_encryption.dart +++ b/packages/at_client/lib/src/encryption_service/abstract_atkey_encryption.dart @@ -1,3 +1,4 @@ +import 'package:at_chops/at_chops.dart'; import 'package:at_client/at_client.dart'; import 'package:at_client/src/client/secondary.dart'; import 'package:at_client/src/encryption_service/encryption.dart'; @@ -9,7 +10,6 @@ import 'package:at_commons/at_builders.dart'; import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart'; import 'package:at_utils/at_logger.dart'; import 'package:meta/meta.dart'; -import 'package:at_chops/at_chops.dart'; /// Contains the common code for [SharedKeyEncryption] and [StreamEncryption] abstract class AbstractAtKeyEncryption implements AtKeyEncryption { @@ -49,8 +49,15 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { if (storeSharedKeyEncryptedWithData) { atKey.metadata.sharedKeyEnc = theirEncryptedSymmetricKeyCopy; + // This is a legacy checksum with MD5 algo. atKey.metadata.pubKeyCS = EncryptionUtil.md5CheckSum(await _getSharedWithPublicKey(atKey)); + // Hashed the encryption public key with sha512. This is to ensure the encryption + // public key of the receiver are same during encryption and decryption process. + String hash = await AtChops.hashWith(HashingAlgoType.sha512) + .hash((await _getSharedWithPublicKey(atKey)).codeUnits); + atKey.metadata.pubKeyHash = + PublicKeyHash(hash, HashingAlgoType.sha512.name); } } diff --git a/packages/at_client/lib/src/preference/at_client_preference.dart b/packages/at_client/lib/src/preference/at_client_preference.dart index 5eeaa8a95..e37b59912 100644 --- a/packages/at_client/lib/src/preference/at_client_preference.dart +++ b/packages/at_client/lib/src/preference/at_client_preference.dart @@ -59,7 +59,7 @@ class AtClientPreference { int syncBatchSize = 5; /// The number of keys to pull from cloud secondary to local secondary in a single call. - int syncPageLimit = 10; + int syncPageLimit = 25; // Default chunk size for file encryption and decryption int fileEncryptionChunkSize = 4096; diff --git a/packages/at_client/lib/src/response/at_notification.dart b/packages/at_client/lib/src/response/at_notification.dart index 05f7bab0b..0e1948088 100644 --- a/packages/at_client/lib/src/response/at_notification.dart +++ b/packages/at_client/lib/src/response/at_notification.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:at_client/at_client.dart'; class AtNotification { @@ -34,6 +36,15 @@ class AtNotification { metadata.skeEncAlgo = json['metadata'][AtConstants.sharedKeyEncryptedEncryptingAlgo]; metadata.sharedKeyEnc = json['metadata'][AtConstants.sharedKeyEncrypted]; + // AtContants.sharedWithPublicKeyHash will be sent by the server starting v3.0.52 + // Notifications received from Secondary server before 3.0.52 does not contain + // AtConstants.sharedWithPublicKeyHash. Therefore, check for null. + if (json['metadata'][AtConstants.sharedWithPublicKeyHash] != null) { + var publicKeyHash = + jsonDecode(json['metadata'][AtConstants.sharedWithPublicKeyHash]); + metadata.pubKeyHash = + PublicKeyHash(publicKeyHash['hash'], publicKeyHash['hashingAlgo']); + } } return AtNotification(json['id'], json['key'], json['from'], json['to'], diff --git a/packages/at_client/lib/src/service/sync_service_impl.dart b/packages/at_client/lib/src/service/sync_service_impl.dart index 4222267f5..395f86a1e 100644 --- a/packages/at_client/lib/src/service/sync_service_impl.dart +++ b/packages/at_client/lib/src/service/sync_service_impl.dart @@ -553,15 +553,15 @@ class SyncServiceImpl implements SyncService, AtSignChangeListener { Future> _getEntriesToSyncFromServer( int lastReceivedServerCommitId, int serverCommitId, {int? localCommitIdBeforeSync, int? skipDeletesUntil}) async { + // Sync verb syntax has to be changed before removing these deprecations var syncBuilder = SyncVerbBuilder() ..commitId = lastReceivedServerCommitId - ..regex = _atClient.getPreferences()!.syncRegex ..limit = _atClient.getPreferences()!.syncPageLimit - ..isPaginated = true; + ..regex = _atClient.getPreferences()!.syncRegex; if (_shouldSkipDeletes(skipDeletesUntil, serverCommitId)) { syncBuilder.skipDeletesUntil = skipDeletesUntil; } - + _logger.finer(_logger.getLogMessageWithClientParticulars( _atClient.getPreferences()!.atClientParticulars, 'syncBuilder ${syncBuilder.buildCommand()}')); @@ -784,6 +784,13 @@ class SyncServiceImpl implements SyncService, AtSignChangeListener { if (metadata.pubKeyCS != null) { metadataStr += ':pubKeyCS:${metadata.pubKeyCS}'; } + if (metadata.pubKeyHash != null) { + metadataStr += + ':${AtConstants.sharedWithPublicKeyHash}:${metadata.pubKeyHash?.hash}'; + metadataStr += + ':${AtConstants.sharedWithPublicKeyHashingAlgo}:${metadata.pubKeyHash?.hashingAlgo}'; + } + if (metadata.encoding != null) { metadataStr += ':encoding:${metadata.encoding}'; } @@ -811,13 +818,8 @@ class SyncServiceImpl implements SyncService, AtSignChangeListener { ///Throws [AtClientException] if cloud secondary is not reachable @override Future isInSync() async { - late RemoteSecondary remoteSecondary; try { - remoteSecondary = RemoteSecondary( - _atClient.getCurrentAtSign()!, _atClient.getPreferences()!, - atChops: _atClient.atChops); - var serverCommitId = - await _getServerCommitId(remoteSecondary: remoteSecondary); + var serverCommitId = await _getServerCommitId(); var lastReceivedServerCommitId = await getLastReceivedServerCommitId(); @@ -838,8 +840,6 @@ class SyncServiceImpl implements SyncService, AtSignChangeListener { var cause = (e is AtException) ? e.getTraceMessage() : e.toString(); _logger.severe('exception in isInSync $cause'); throw AtClientException.message(e.toString()); - } finally { - unawaited(remoteSecondary.atLookUp.close()); } } @@ -848,8 +848,7 @@ class SyncServiceImpl implements SyncService, AtSignChangeListener { _logger.finest('*** isInSync..sync in progress'); return true; } - var serverCommitId = - await _getServerCommitId(remoteSecondary: _remoteSecondary); + var serverCommitId = await _getServerCommitId(); var lastReceivedServerCommitId = await getLastReceivedServerCommitId(); var lastSyncedEntry = await syncUtil.getLastSyncedEntry( _atClient.getPreferences()!.syncRegex, @@ -867,11 +866,10 @@ class SyncServiceImpl implements SyncService, AtSignChangeListener { /// Returns the cloud secondary latest commit id. if null, returns -1. ///Throws [AtLookUpException] if secondary is not reachable - Future _getServerCommitId({RemoteSecondary? remoteSecondary}) async { - remoteSecondary ??= _remoteSecondary; + Future _getServerCommitId() async { // ignore: no_leading_underscores_for_local_identifiers var _serverCommitId = await syncUtil.getLatestServerCommitId( - remoteSecondary, _atClient.getPreferences()!.syncRegex); + _remoteSecondary, _atClient.getPreferences()!.syncRegex); // If server commit id is null, set to -1; _serverCommitId ??= -1; _logger.info(_logger.getLogMessageWithClientParticulars( @@ -1015,6 +1013,12 @@ class SyncServiceImpl implements SyncService, AtSignChangeListener { builder.atKey.metadata.pubKeyCS = metaData[AtConstants.sharedWithPublicKeyCheckSum]; } + if (metaData[AtConstants.sharedWithPublicKeyHash] != null) { + Map pubKeyHash = + jsonDecode(metaData[AtConstants.sharedWithPublicKeyHash]); + builder.atKey.metadata.pubKeyHash = + PublicKeyHash(pubKeyHash['hash'], pubKeyHash['hashingAlgo']); + } if (metaData[AtConstants.encoding] != null) { builder.atKey.metadata.encoding = metaData[AtConstants.encoding]; } @@ -1036,6 +1040,13 @@ class SyncServiceImpl implements SyncService, AtSignChangeListener { builder.atKey.metadata.skeEncAlgo = metaData[AtConstants.sharedKeyEncryptedEncryptingAlgo]; } + + if (metaData[AtConstants.sharedWithPublicKeyHash] != null && + metaData[AtConstants.sharedWithPublicKeyHashingAlgo] != null) { + builder.atKey.metadata.pubKeyHash = PublicKeyHash( + metaData[AtConstants.sharedWithPublicKeyHash], + metaData[AtConstants.sharedWithPublicKeyHashingAlgo]); + } } } diff --git a/packages/at_client/lib/src/transformer/request_transformer/notify_request_transformer.dart b/packages/at_client/lib/src/transformer/request_transformer/notify_request_transformer.dart index 5fd9df181..6ad1a359d 100644 --- a/packages/at_client/lib/src/transformer/request_transformer/notify_request_transformer.dart +++ b/packages/at_client/lib/src/transformer/request_transformer/notify_request_transformer.dart @@ -2,13 +2,13 @@ import 'dart:async'; +import 'package:at_client/src/encryption_service/encryption.dart'; import 'package:at_client/src/preference/at_client_preference.dart'; import 'package:at_client/src/service/notification_service.dart'; import 'package:at_client/src/transformer/at_transformer.dart'; import 'package:at_client/src/util/at_client_util.dart'; import 'package:at_commons/at_builders.dart'; import 'package:at_commons/at_commons.dart'; -import 'package:at_client/src/encryption_service/encryption.dart'; /// Class is responsible for taking the [NotificationParams] and converting into [NotifyVerbBuilder] class NotificationRequestTransformer @@ -96,6 +96,8 @@ class NotificationRequestTransformer notificationParams.atKey.metadata.skeEncKeyName; builder.atKey.metadata.skeEncAlgo = notificationParams.atKey.metadata.skeEncAlgo; + builder.atKey.metadata.pubKeyHash = + notificationParams.atKey.metadata.pubKeyHash; } Future _encryptNotificationValue(AtKey atKey, String value) async { diff --git a/packages/at_client/lib/src/util/at_client_util.dart b/packages/at_client/lib/src/util/at_client_util.dart index a571c5329..20f7c658d 100644 --- a/packages/at_client/lib/src/util/at_client_util.dart +++ b/packages/at_client/lib/src/util/at_client_util.dart @@ -105,6 +105,8 @@ class AtClientUtil { metadataMap[AtConstants.sharedKeyEncryptedEncryptingAlgo]; metadata.isPublic = isPublic; metadata.isCached = isCached; + metadata.pubKeyHash = PublicKeyHash.fromJson( + metadataMap[AtConstants.sharedWithPublicKeyHash]); return metadata; } diff --git a/packages/at_client/pubspec.yaml b/packages/at_client/pubspec.yaml index 06912b39c..9de659fc7 100644 --- a/packages/at_client/pubspec.yaml +++ b/packages/at_client/pubspec.yaml @@ -31,19 +31,19 @@ dependencies: async: ^2.9.0 at_utf7: ^1.0.0 at_base2e15: ^1.0.0 - at_commons: ^5.1.1 + at_commons: ^5.1.2 at_utils: ^3.0.19 at_chops: ^2.2.0 at_lookup: ^3.0.49 - at_auth: ^2.0.7 + at_auth: ^2.0.10 at_persistence_spec: ^2.0.14 at_persistence_secondary_server: ^3.1.0 meta: ^1.8.0 version: ^3.0.2 dev_dependencies: - lints: ^4.0.0 - test: ^1.21.4 + lints: ^5.0.0 + test: ^1.25.8 at_demo_data: ^1.0.1 coverage: ^1.5.0 mocktail: ^1.0.3 diff --git a/packages/at_client/test/encryption_decryption_test.dart b/packages/at_client/test/encryption_decryption_test.dart new file mode 100644 index 000000000..58f6a4494 --- /dev/null +++ b/packages/at_client/test/encryption_decryption_test.dart @@ -0,0 +1,170 @@ +import 'dart:io'; + +import 'package:at_chops/at_chops.dart'; +import 'package:at_client/at_client.dart'; +import 'package:at_client/src/decryption_service/shared_key_decryption.dart'; +import 'package:at_client/src/encryption_service/shared_key_encryption.dart'; +import 'package:at_commons/at_builders.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +class MockRemoteSecondary extends Mock implements RemoteSecondary {} + +void main() { + String currentAtSign = '@alice'; + String namespace = 'unit.test'; + + MockRemoteSecondary mockRemoteSecondary = MockRemoteSecondary(); + + late AtChops atChops; + late AtClient atClient; + + setUp(() async { + AtClientPreference atClientPreference = AtClientPreference() + ..isLocalStoreRequired = true + ..hiveStoragePath = 'test/unit_test_storage/hive' + ..commitLogPath = 'test/unit_test_storage/commit'; + + AtEncryptionKeyPair atEncryptionKeyPair = + AtChopsUtil.generateAtEncryptionKeyPair(); + AtPkamKeyPair atPkamKeyPair = AtChopsUtil.generateAtPkamKeyPair(); + AtChopsKeys atChopsKeys = + AtChopsKeys.create(atEncryptionKeyPair, atPkamKeyPair); + atChopsKeys.selfEncryptionKey = + AtChopsUtil.generateSymmetricKey(EncryptionKeyType.aes256); + atChops = AtChopsImpl(atChopsKeys); + + atClient = await AtClientImpl.create( + currentAtSign, namespace, atClientPreference, + remoteSecondary: mockRemoteSecondary, atChops: atChops); + + // During decryption, fetches the encryption public key from local keystore. + // So, store the encryption public key into local secondary keystore. + await atClient.getLocalSecondary()?.putValue( + 'public:publickey$currentAtSign', + atEncryptionKeyPair.atPublicKey.publicKey); + }); + + tearDownAll(() { + Directory('test/unit_test_storage').deleteSync(recursive: true); + }); + + group('A group of tests related to encryption and decryption of shared keys', + () { + test( + 'A test to verify encryption and decryption of shared key is successful - with publicKeyHash', + () async { + registerFallbackValue(LLookupVerbBuilder()); + AtKey sharedKey = + (AtKey.shared('email', namespace: namespace, sharedBy: currentAtSign) + ..sharedWith('@bob')) + .build(); + String value = 'alice@atsign.com'; + + when(() => mockRemoteSecondary + .executeVerb(any(that: EncryptedSharedKeyMatcher()))) + .thenAnswer((_) => Future.value('data:null')); + + // Returns encryption public key of sharedWith atSign. + // For unit test, reusing the current AtSign encryptionPublicKey. + when(() => mockRemoteSecondary + .executeVerb(any(that: EncryptionPublicKeyMatcher()))) + .thenAnswer((_) => Future.value( + atChops.atChopsKeys.atEncryptionKeyPair?.atPublicKey.publicKey)); + + // Encryption + SharedKeyEncryption sharedKeyEncryption = SharedKeyEncryption(atClient); + String encryptedValue = + await sharedKeyEncryption.encrypt(sharedKey, value); + expect(sharedKey.metadata.pubKeyHash?.hash.isNotEmpty, true); + expect(sharedKey.metadata.pubKeyHash?.hashingAlgo, 'sha512'); + expect(sharedKey.metadata.sharedKeyEnc?.isNotEmpty, true); + expect(sharedKey.metadata.pubKeyCS?.isNotEmpty, true); + + // Explicitly setting pubKeyCS to null, so that pubKeyHash will only be used + // during decryption. + sharedKey.metadata.pubKeyCS = null; + expect(sharedKey.metadata.pubKeyCS, null); + + // Decryption + SharedKeyDecryption sharedKeyDecryption = SharedKeyDecryption(atClient); + String decryptedValue = + await sharedKeyDecryption.decrypt(sharedKey, encryptedValue); + expect(decryptedValue, value); + }); + + test('A test to verify exception is thrown when publicKeyHash mismatch', + () async { + registerFallbackValue(LLookupVerbBuilder()); + AtKey sharedKey = + (AtKey.shared('email', namespace: namespace, sharedBy: currentAtSign) + ..sharedWith('@bob')) + .build(); + String value = 'alice@atsign.com'; + + when(() => mockRemoteSecondary + .executeVerb(any(that: EncryptedSharedKeyMatcher()))) + .thenAnswer((_) => Future.value('data:null')); + + // Returns encryption public key of sharedWith atSign. + // For unit test, reusing the current AtSign encryptionPublicKey. + when(() => mockRemoteSecondary + .executeVerb(any(that: EncryptionPublicKeyMatcher()))) + .thenAnswer((_) => Future.value( + atChops.atChopsKeys.atEncryptionKeyPair?.atPublicKey.publicKey)); + + // Encryption + SharedKeyEncryption sharedKeyEncryption = SharedKeyEncryption(atClient); + String encryptedValue = + await sharedKeyEncryption.encrypt(sharedKey, value); + expect(sharedKey.metadata.pubKeyHash?.hash.isNotEmpty, true); + expect(sharedKey.metadata.pubKeyHash?.hashingAlgo, 'sha512'); + expect(sharedKey.metadata.sharedKeyEnc?.isNotEmpty, true); + expect(sharedKey.metadata.pubKeyCS?.isNotEmpty, true); + + // Explicitly setting pubKeyCS to null, so that pubKeyHash will only be used + // during decryption. + sharedKey.metadata.pubKeyCS = null; + expect(sharedKey.metadata.pubKeyCS, null); + // Explicity changing the publicKeyHash value to mimic change in publicKeyHash + // value. + sharedKey.metadata.pubKeyHash = + PublicKeyHash('dummy_hash_value', HashingAlgoType.sha512.name); + + // Decryption + SharedKeyDecryption sharedKeyDecryption = SharedKeyDecryption(atClient); + expect( + () async => + await sharedKeyDecryption.decrypt(sharedKey, encryptedValue), + throwsA(predicate((dynamic e) => + e is AtPublicKeyChangeException && + e.message == + 'Public key has changed. Cannot decrypt shared key ${sharedKey.toString()}'))); + }); + }); +} + +class EncryptedSharedKeyMatcher extends Matcher { + @override + Description describe(Description description) { + return description; + } + + @override + bool matches(item, Map matchState) { + return item.atKey.key.startsWith(AtConstants.atEncryptionSharedKey); + } +} + +class EncryptionPublicKeyMatcher extends Matcher { + @override + Description describe(Description description) { + // TODO: implement describe + throw UnimplementedError(); + } + + @override + bool matches(item, Map matchState) { + return item.atKey.key.startsWith('publickey'); + } +} diff --git a/tests/at_end2end_test/pubspec.yaml b/tests/at_end2end_test/pubspec.yaml index fd1a4f012..33fb13150 100644 --- a/tests/at_end2end_test/pubspec.yaml +++ b/tests/at_end2end_test/pubspec.yaml @@ -15,6 +15,6 @@ dependencies: path: ../../packages/at_client dev_dependencies: - test: ^1.24.3 + test: ^1.25.8 lints: ^2.0.0 coverage: ^1.5.0 diff --git a/tests/at_end2end_test/test/enrollment_setup.dart b/tests/at_end2end_test/test/enrollment_setup.dart index 666c14770..bc678b071 100644 --- a/tests/at_end2end_test/test/enrollment_setup.dart +++ b/tests/at_end2end_test/test/enrollment_setup.dart @@ -191,6 +191,7 @@ Future getDefaultEncryptionPrivateKey( var privateKeyCommand = 'keys:get:keyName:$enrollmentIdFromServer.${AtConstants.defaultEncryptionPrivateKey}.__manage$atSign'; String encryptionPrivateKeyFromServer; + String encryptionPrivateKeyIV; try { var getPrivateKeyResult = await atLookUp.executeCommand('$privateKeyCommand\n', auth: true); @@ -200,13 +201,15 @@ Future getDefaultEncryptionPrivateKey( getPrivateKeyResult = getPrivateKeyResult.replaceFirst('data:', ''); var privateKeyResultJson = jsonDecode(getPrivateKeyResult); encryptionPrivateKeyFromServer = privateKeyResultJson['value']; + encryptionPrivateKeyIV = privateKeyResultJson['iv']; } on Exception catch (e) { throw AtEnrollmentException( 'Exception while getting encrypted private key/self key from server: $e'); } AtEncryptionResult? atEncryptionResult = atLookUp.atChops?.decryptString( encryptionPrivateKeyFromServer, EncryptionKeyType.aes256, - keyName: 'apkamSymmetricKey', iv: AtChopsUtil.generateIVLegacy()); + keyName: 'apkamSymmetricKey', + iv: AtChopsUtil.generateIVFromBase64String(encryptionPrivateKeyIV)); return atEncryptionResult?.result; } @@ -218,6 +221,7 @@ Future getDefaultSelfEncryptionKey( var selfEncryptionKeyCommand = 'keys:get:keyName:$enrollmentIdFromServer.${AtConstants.defaultSelfEncryptionKey}.__manage$atSign'; String selfEncryptionKeyFromServer; + String selfEncryptionKeyIV; try { String? encryptedSelfEncryptionKey = await atLookUp .executeCommand('$selfEncryptionKeyCommand\n', auth: true); @@ -230,12 +234,14 @@ Future getDefaultSelfEncryptionKey( encryptedSelfEncryptionKey.replaceFirst('data:', ''); var selfEncryptionKeyResultJson = jsonDecode(encryptedSelfEncryptionKey); selfEncryptionKeyFromServer = selfEncryptionKeyResultJson['value']; + selfEncryptionKeyIV = selfEncryptionKeyResultJson['iv']; } on Exception catch (e) { throw AtEnrollmentException( 'Exception while getting encrypted private key/self key from server: $e'); } AtEncryptionResult? atEncryptionResult = atLookUp.atChops?.decryptString( selfEncryptionKeyFromServer, EncryptionKeyType.aes256, - keyName: 'apkamSymmetricKey', iv: AtChopsUtil.generateIVLegacy()); + keyName: 'apkamSymmetricKey', + iv: AtChopsUtil.generateIVFromBase64String(selfEncryptionKeyIV)); return atEncryptionResult?.result; } diff --git a/tests/at_functional_test/pubspec.yaml b/tests/at_functional_test/pubspec.yaml index bd4a441a4..d7cfd2647 100644 --- a/tests/at_functional_test/pubspec.yaml +++ b/tests/at_functional_test/pubspec.yaml @@ -15,6 +15,6 @@ dependencies: at_lookup: ^3.0.49 dev_dependencies: - test: ^1.24.3 - lints: ^2.0.0 + test: ^1.25.8 + lints: ^5.0.0 coverage: ^1.5.0 \ No newline at end of file diff --git a/tests/at_functional_test/test/atclient_sharedkey_test.dart b/tests/at_functional_test/test/atclient_sharedkey_test.dart index dbcbf3f3f..e8411d96a 100644 --- a/tests/at_functional_test/test/atclient_sharedkey_test.dart +++ b/tests/at_functional_test/test/atclient_sharedkey_test.dart @@ -3,6 +3,7 @@ import 'package:at_client/src/encryption_service/encryption_manager.dart'; import 'package:at_functional_test/src/config_util.dart'; import 'package:at_functional_test/src/sync_service.dart'; import 'package:test/test.dart'; + import 'test_utils.dart'; void main() { @@ -29,6 +30,8 @@ void main() { var metadata = await atClient.getMeta(phoneKey); expect(metadata!.sharedKeyEnc, isNotEmpty); expect(metadata.pubKeyCS, isNotEmpty); + expect(metadata.pubKeyHash?.hash, isNotEmpty); + expect(metadata.pubKeyHash?.hashingAlgo, isNotEmpty); }); test('sharedKey and checksum metadata sync to local storage', () async { @@ -42,7 +45,7 @@ void main() { AtKeyEncryptionManager(atClient).get(phoneKey, currentAtSign); var encryptedValue = await encryptionService.encrypt(phoneKey, value); var result = await atClient.getRemoteSecondary()!.executeCommand( - 'update:sharedKeyEnc:${phoneKey.metadata.sharedKeyEnc}:pubKeyCS:${phoneKey.metadata.pubKeyCS}:${phoneKey.sharedWith}:${phoneKey.key}.$namespace$currentAtSign $encryptedValue\n', + 'update:sharedKeyEnc:${phoneKey.metadata.sharedKeyEnc}:pubKeyCS:${phoneKey.metadata.pubKeyCS}:pubKeyHash:${phoneKey.metadata.pubKeyHash?.hash}:hashingAlgo:${phoneKey.metadata.pubKeyHash?.hashingAlgo}:${phoneKey.sharedWith}:${phoneKey.key}.$namespace$currentAtSign $encryptedValue\n', auth: true); expect(result != null, true); await FunctionalTestSyncService.getInstance() @@ -50,5 +53,7 @@ void main() { var metadata = await atClient.getMeta(phoneKey); expect(metadata?.sharedKeyEnc, isNotEmpty); expect(metadata?.pubKeyCS, isNotEmpty); + expect(metadata?.pubKeyHash?.hash, isNotEmpty); + expect(metadata?.pubKeyHash?.hashingAlgo, isNotEmpty); }); } diff --git a/tests/at_functional_test/test/testData/cert.pem b/tests/at_functional_test/test/testData/cert.pem index 977346257..0804758f7 100644 --- a/tests/at_functional_test/test/testData/cert.pem +++ b/tests/at_functional_test/test/testData/cert.pem @@ -1,29 +1,29 @@ -----BEGIN CERTIFICATE----- -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 -u9qdIVKAbK+1cN15HYJ5jG0pXrOsBTbr +MAowCAYGZ4EMAQIBMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHYAzPsPaoVxCWX+ +lZtTzumyfCLphVwNl422qX5UwP5MDbAAAAGTyUNR5wAABAMARzBFAiEA+qSCaeFM +SdWNtOBMFlHJqd8KuaXE03TW0lkAwXmilvYCIGsvyis+wdeiI6U/0fXO3aSfQLw1 +BVUuEdbQzTxrAvN1AHYAE0rfGrWYQgl4DG/vTHqRpBa3I0nOWFdq367ap8Kr4CIA +AAGTyUNSnAAABAMARzBFAiBtvdHsFEXPsU2E2SgLoZhhuOK8K3tDRuBtQ3PqZJMq +ogIhAI8jQXRm3dg0WRvPoxnp72gcg0yKRHvAH6L/9qwo067JMA0GCSqGSIb3DQEB +CwUAA4IBAQBrbAzdOo8akOc1s0jevoTFyn11cPSkkjnic0x93tcuoGkbs5oqJpVh +PuLDSXOy+xJ/TXUIDGFbCLA/SWC7/b1vViqwasEBGohJJ7ZM/27j/fIL+6Nc/u7Y +f37L5J32WcGRKKoxxA9MoaKqn5GVZTW8UIy/K+llfRaSptQCloyOPcq0h9uTyFci +sIDhVb5qj9hMmgkcz0BfZZHqH+45vtnFV0v+jeLGRkijcxN2LxpXIHCFTDivl0tS +/o2XXHuXZCkZ6q1eeDF7kC6fccXLOJnsbiNwmwiEQb/0WeX/lDQzA31sv4yOVfZr +Ne1IRWUsZfbo8jKSnnUOCwCKgOoNq81z -----END CERTIFICATE-----