From a6ae6b769885905176107d7c093a221850052d12 Mon Sep 17 00:00:00 2001 From: Murali Date: Thu, 14 Dec 2023 21:04:24 +0530 Subject: [PATCH 1/5] fix: replace encryption util methods in local key decryption --- .../local_key_decryption.dart | 16 ++- .../abstract_atkey_encryption.dart | 40 +++---- .../test/local_key_decryption_test.dart | 107 ++++++++++++++++++ 3 files changed, 141 insertions(+), 22 deletions(-) create mode 100644 packages/at_client/test/local_key_decryption_test.dart diff --git a/packages/at_client/lib/src/decryption_service/local_key_decryption.dart b/packages/at_client/lib/src/decryption_service/local_key_decryption.dart index 052877fc7..487bd4acc 100644 --- a/packages/at_client/lib/src/decryption_service/local_key_decryption.dart +++ b/packages/at_client/lib/src/decryption_service/local_key_decryption.dart @@ -1,3 +1,4 @@ +import 'package:at_chops/at_chops.dart'; import 'package:at_client/at_client.dart'; import 'package:at_client/src/decryption_service/decryption.dart'; import 'package:at_client/src/encryption_service/abstract_atkey_encryption.dart'; @@ -32,7 +33,18 @@ class LocalKeyDecryption extends AbstractAtKeyEncryption intent: Intent.fetchEncryptionSharedKey, exceptionScenario: ExceptionScenario.fetchEncryptionKeys); } - return EncryptionUtil.decryptValue(encryptedValue, symmetricKey, - ivBase64: atKey.metadata?.ivNonce); + var iV; + if (atKey.metadata?.ivNonce != null) { + iV = AtChopsUtil.generateIVFromBase64String(atKey.metadata!.ivNonce!); + } else { + iV = AtChopsUtil.generateIVLegacy(); + } + var encryptionAlgo = AESEncryptionAlgo(AESKey(symmetricKey)); + final decryptionResultFromAtChops = super.atClient.atChops!.decryptString( + encryptedValue, EncryptionKeyType.aes256, + encryptionAlgorithm: encryptionAlgo, iv: iV); + _logger.finer( + 'decryptionResultFromAtChops: ${decryptionResultFromAtChops.result}'); + return decryptionResultFromAtChops.result; } } 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 85d581f23..3d8434874 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 @@ -15,16 +15,16 @@ import 'package:at_chops/at_chops.dart'; abstract class AbstractAtKeyEncryption implements AtKeyEncryption { late final AtSignLogger _logger; late String _sharedKey; - final AtClient _atClient; + final AtClient atClient; AtCommitLog? atCommitLog; DefaultResponseParser defaultResponseParser = DefaultResponseParser(); String get sharedKey => _sharedKey; - AbstractAtKeyEncryption(this._atClient) { + AbstractAtKeyEncryption(this.atClient) { _logger = AtSignLogger( - 'AbstractAtKeyEncryption (${_atClient.getCurrentAtSign()})'); + 'AbstractAtKeyEncryption (${atClient.getCurrentAtSign()})'); } SyncUtil syncUtil = SyncUtil(); @@ -68,7 +68,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { try { /// Look first in local storage encryptedSharedKey = await _getMyEncryptedCopyOfSharedSymmetricKey( - _atClient.getLocalSecondary()!, atKey); + atClient.getLocalSecondary()!, atKey); } on KeyNotFoundException { encryptedSharedKey = null; } @@ -81,17 +81,17 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { // Defensive code to ensure that 'their' copy is not in local storage // if 'our' copy is not in local storage await deleteTheirCopyOfEncryptedSharedKey( - atKey, _atClient.getLocalSecondary()!); + atKey, atClient.getLocalSecondary()!); _logger.info( 'Fetching shared symmetric key for ${atKey.sharedBy} from atServer'); encryptedSharedKey = await _getMyEncryptedCopyOfSharedSymmetricKey( - _atClient.getRemoteSecondary()!, atKey); + atClient.getRemoteSecondary()!, atKey); if (encryptedSharedKey != null && encryptedSharedKey != 'data:null') { // If found on atServer, save to local _logger.info( 'Retrieved my encrypted copy of shared symmetric key for ${atKey.sharedWith} from atServer - saving to local storage'); await _storeMyEncryptedCopyOfSharedSymmetricKey( - atKey, encryptedSharedKey, _atClient.getLocalSecondary()!); + atKey, encryptedSharedKey, atClient.getLocalSecondary()!); } } } on KeyNotFoundException { @@ -107,7 +107,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { /// - If found existing in either local or atServer, decrypt it and return encryptedSharedKey = defaultResponseParser.parse(encryptedSharedKey!).response; - final decryptionResult = _atClient.atChops! + final decryptionResult = atClient.atChops! .decryptString(encryptedSharedKey, EncryptionKeyType.rsa2048); return decryptionResult.result; } @@ -124,7 +124,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { // Fetch our encryption public key String? currentAtSignEncryptionPublicKey; try { - currentAtSignEncryptionPublicKey = await _atClient + currentAtSignEncryptionPublicKey = await atClient .getLocalSecondary()! .getEncryptionPublicKey(atKey.sharedBy!); } on KeyNotFoundException catch (e) { @@ -143,14 +143,14 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { // Defensive code to ensure that we do not have an old 'their' copy on atServer await deleteTheirCopyOfEncryptedSharedKey( - atKey, _atClient.getRemoteSecondary()!); + atKey, atClient.getRemoteSecondary()!); // Store my copy for future use // First, store to atServer // try { _logger.info("Storing new shared symmetric key to atServer"); await _storeMyEncryptedCopyOfSharedSymmetricKey( - atKey, encryptedSharedKeyMyCopy, _atClient.getRemoteSecondary()!); + atKey, encryptedSharedKeyMyCopy, atClient.getRemoteSecondary()!); // // TODO // } on KeyAlreadyExistsException catch (e) { // return await getMyCopyOfSharedSymmetricKey(atKey); @@ -159,7 +159,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { // Now store to local _logger.info("Storing new shared symmetric key to local storage"); await _storeMyEncryptedCopyOfSharedSymmetricKey( - atKey, encryptedSharedKeyMyCopy, _atClient.getLocalSecondary()!); + atKey, encryptedSharedKeyMyCopy, atClient.getLocalSecondary()!); // Return the unencrypted symmetric key return newSymmetricKeyBase64; @@ -178,7 +178,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { /// - Check if encrypted copy exists in local storage String? theirEncryptedCopy = await _getTheirEncryptedCopyOfSharedSymmetricKey( - _atClient.getLocalSecondary()!, atKey); + atClient.getLocalSecondary()!, atKey); // Found it in local storage. Return it. if (theirEncryptedCopy != null) { return theirEncryptedCopy; @@ -188,7 +188,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { _logger.info("'Their' copy of shared symmetric key for ${atKey.sharedWith}" " not found in local storage - will check atServer"); theirEncryptedCopy = await _getTheirEncryptedCopyOfSharedSymmetricKey( - _atClient.getRemoteSecondary()!, atKey); + atClient.getRemoteSecondary()!, atKey); /// - If in atServer, save to local storage and return if (theirEncryptedCopy != null) { @@ -197,7 +197,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { " in atServer - saving to local storage"); await storeTheirCopyOfEncryptedSharedKeyToSecondary( atKey, theirEncryptedCopy, - secondary: _atClient.getLocalSecondary()!); + secondary: atClient.getLocalSecondary()!); return theirEncryptedCopy; } @@ -229,14 +229,14 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { "Saving 'their' copy of shared symmetric key for ${atKey.sharedWith} to atServer"); await storeTheirCopyOfEncryptedSharedKeyToSecondary( atKey, theirEncryptedCopy, - secondary: _atClient.getRemoteSecondary()!); + secondary: atClient.getRemoteSecondary()!); /// - (c) save encrypted copy to local storage and return _logger.info( "Saving 'their' copy of shared symmetric key for ${atKey.sharedWith} to local storage"); await storeTheirCopyOfEncryptedSharedKeyToSecondary( atKey, theirEncryptedCopy, - secondary: _atClient.getLocalSecondary()!); + secondary: atClient.getLocalSecondary()!); return theirEncryptedCopy; } @@ -253,7 +253,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { ..isPublic = true ..isCached = true; - sharedWithPublicKey = await _atClient + sharedWithPublicKey = await atClient .getLocalSecondary()! .executeVerb(cachedEncryptionPublicKeyBuilder); } on KeyNotFoundException { @@ -264,7 +264,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { var encryptionPublicKeyBuilder = PLookupVerbBuilder() ..atKey = 'publickey' ..sharedBy = atKey.sharedWith; - sharedWithPublicKey = await _atClient + sharedWithPublicKey = await atClient .getRemoteSecondary()! .executeVerb(encryptionPublicKeyBuilder); } @@ -344,7 +344,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { Future storeTheirCopyOfEncryptedSharedKeyToSecondary( AtKey atKey, String encryptedSharedKeyValue, {Secondary? secondary}) async { - secondary ??= _atClient.getLocalSecondary()!; + secondary ??= atClient.getLocalSecondary()!; var updateSharedKeyBuilder = UpdateVerbBuilder() ..atKey = AtConstants.atEncryptionSharedKey ..sharedWith = atKey.sharedWith diff --git a/packages/at_client/test/local_key_decryption_test.dart b/packages/at_client/test/local_key_decryption_test.dart new file mode 100644 index 000000000..2c2f06036 --- /dev/null +++ b/packages/at_client/test/local_key_decryption_test.dart @@ -0,0 +1,107 @@ +import 'package:at_chops/at_chops.dart'; +import 'package:at_client/src/decryption_service/local_key_decryption.dart'; +import 'package:at_commons/at_builders.dart'; +import 'package:test/test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:at_client/at_client.dart'; +import 'package:at_lookup/at_lookup.dart'; + +class MockAtClientImpl extends Mock implements AtClient {} + +class MockLocalSecondary extends Mock implements LocalSecondary {} + +class MockRemoteSecondary extends Mock implements RemoteSecondary {} + +class MockAtLookupImpl extends Mock implements AtLookUp {} + +class FakeLocalLookUpVerbBuilder extends Fake implements LLookupVerbBuilder {} + +class FakeDeleteVerbBuilder extends Fake implements DeleteVerbBuilder {} + +void main() { + AtClient mockAtClient = MockAtClientImpl(); + AtLookUp mockAtLookUp = MockAtLookupImpl(); + LocalSecondary mockLocalSecondary = MockLocalSecondary(); + RemoteSecondary mockRemoteSecondary = MockRemoteSecondary(); + setUp(() { + reset(mockAtLookUp); + when(() => mockAtClient.getLocalSecondary()) + .thenAnswer((_) => mockLocalSecondary); + + registerFallbackValue(FakeLocalLookUpVerbBuilder()); + registerFallbackValue(FakeDeleteVerbBuilder()); + }); + group('A group of local key decryption tests', () { + test('test to verify decryption of local key', () async { + var rsaKeyPair = AtChopsUtil.generateAtEncryptionKeyPair(); + var sharedSymmetricKey = 'REqkIcl9HPekt0T7+rZhkrBvpysaPOeC2QL1PVuWlus='; + var encryptedSharedSymmetricKey = EncryptionUtil.encryptKey( + sharedSymmetricKey, rsaKeyPair.atPublicKey.publicKey); + AtChopsKeys atChopsKeys = AtChopsKeys.create(rsaKeyPair, null); + var atChopsImpl = AtChopsImpl(atChopsKeys); + mockAtClient.atChops = atChopsImpl; + when(() => mockAtClient.atChops).thenAnswer((_) => atChopsImpl); + print('encryptedSharedSymmetricKey:$encryptedSharedSymmetricKey'); + when(() => mockAtClient.getLocalSecondary()) + .thenReturn(mockLocalSecondary); + when(() => mockLocalSecondary.executeVerb(any())) + .thenAnswer((_) => Future.value('data:$encryptedSharedSymmetricKey')); + var localKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@bob' + ..key = 'shared_key'; + var testValue = 'abc!@123'; + var encryptedTestValue = + EncryptionUtil.encryptValue(testValue, sharedSymmetricKey); + var localKeyDecryption = LocalKeyDecryption(mockAtClient); + var decryptedTestValue = + await localKeyDecryption.decrypt(localKey, encryptedTestValue); + expect(decryptedTestValue, testValue); + }); + + test('test to check AtDecryptionException when encrypted value is null', + () async { + var localKeyDecryption = LocalKeyDecryption(mockAtClient); + var localKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@bob' + ..key = 'shared_key'; + expect( + () async => await localKeyDecryption.decrypt(localKey, null), + throwsA(predicate((e) => + e is AtDecryptionException && + e.message == 'Decryption failed. Encrypted value is null'))); + }); + + test( + 'test to check SharedKeyNotFoundException when shared key is empty/null', + () async { + var sharedSymmetricKey = 'REqkIcl9HPekt0T7+rZhkrBvpysaPOeC2QL1PVuWlus='; + when(() => mockAtClient.getLocalSecondary()) + .thenReturn(mockLocalSecondary); + when(() => mockAtClient.getRemoteSecondary()) + .thenReturn(mockRemoteSecondary); + when(() => mockLocalSecondary.executeVerb(any(), + sync: false)).thenAnswer((_) => Future.value('data:-1')); + when(() => mockLocalSecondary.executeVerb(any())) + .thenAnswer((_) => Future.value('data:null')); + when(() => mockRemoteSecondary.executeVerb(any())) + .thenAnswer((_) => Future.value('data:null')); + + var localKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@bob' + ..key = 'shared_key'; + var testValue = 'abc!@123'; + var encryptedTestValue = + EncryptionUtil.encryptValue(testValue, sharedSymmetricKey); + var localKeyDecryption = LocalKeyDecryption(mockAtClient); + expect( + () async => + await localKeyDecryption.decrypt(localKey, encryptedTestValue), + throwsA(predicate((e) => + e is SharedKeyNotFoundException && + e.message == 'Empty or null SharedKey is found'))); + }); + }); +} From f5ca540f3b1bee2e3bbcc9872277a629de95c969 Mon Sep 17 00:00:00 2001 From: Murali Date: Thu, 14 Dec 2023 21:06:26 +0530 Subject: [PATCH 2/5] fix: analyzer issue --- .../lib/src/decryption_service/local_key_decryption.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/at_client/lib/src/decryption_service/local_key_decryption.dart b/packages/at_client/lib/src/decryption_service/local_key_decryption.dart index 487bd4acc..47c723f6a 100644 --- a/packages/at_client/lib/src/decryption_service/local_key_decryption.dart +++ b/packages/at_client/lib/src/decryption_service/local_key_decryption.dart @@ -33,7 +33,7 @@ class LocalKeyDecryption extends AbstractAtKeyEncryption intent: Intent.fetchEncryptionSharedKey, exceptionScenario: ExceptionScenario.fetchEncryptionKeys); } - var iV; + InitialisationVector iV; if (atKey.metadata?.ivNonce != null) { iV = AtChopsUtil.generateIVFromBase64String(atKey.metadata!.ivNonce!); } else { From ce2bea1b498288645117be9eafb48b658ba9e835 Mon Sep 17 00:00:00 2001 From: Murali Date: Tue, 19 Dec 2023 14:19:12 +0530 Subject: [PATCH 3/5] feat: unit tests --- .../local_key_decryption.dart | 19 +- .../self_key_decryption.dart | 30 ++- .../shared_key_decryption.dart | 89 ++++---- .../lib/src/util/encryption_util.dart | 2 + .../test/decryption_service_test.dart | 36 +-- .../test/local_key_decryption_test.dart | 1 - .../test/self_key_decryption_test.dart | 112 +++++++++ .../test/shared_key_decryption_test.dart | 216 ++++++++++++++++++ 8 files changed, 422 insertions(+), 83 deletions(-) create mode 100644 packages/at_client/test/self_key_decryption_test.dart create mode 100644 packages/at_client/test/shared_key_decryption_test.dart diff --git a/packages/at_client/lib/src/decryption_service/local_key_decryption.dart b/packages/at_client/lib/src/decryption_service/local_key_decryption.dart index 47c723f6a..84b318672 100644 --- a/packages/at_client/lib/src/decryption_service/local_key_decryption.dart +++ b/packages/at_client/lib/src/decryption_service/local_key_decryption.dart @@ -39,12 +39,19 @@ class LocalKeyDecryption extends AbstractAtKeyEncryption } else { iV = AtChopsUtil.generateIVLegacy(); } - var encryptionAlgo = AESEncryptionAlgo(AESKey(symmetricKey)); - final decryptionResultFromAtChops = super.atClient.atChops!.decryptString( - encryptedValue, EncryptionKeyType.aes256, - encryptionAlgorithm: encryptionAlgo, iv: iV); - _logger.finer( - 'decryptionResultFromAtChops: ${decryptionResultFromAtChops.result}'); + AtEncryptionResult decryptionResultFromAtChops; + try { + var encryptionAlgo = AESEncryptionAlgo(AESKey(symmetricKey)); + decryptionResultFromAtChops = super.atClient.atChops!.decryptString( + encryptedValue, EncryptionKeyType.aes256, + encryptionAlgorithm: encryptionAlgo, iv: iV); + _logger.finer( + 'decryptionResultFromAtChops: ${decryptionResultFromAtChops.result}'); + } on AtDecryptionException catch (e) { + _logger.severe( + 'decryption exception during of key: ${atKey.key}. Reason: ${e.toString()}'); + rethrow; + } return decryptionResultFromAtChops.result; } } diff --git a/packages/at_client/lib/src/decryption_service/self_key_decryption.dart b/packages/at_client/lib/src/decryption_service/self_key_decryption.dart index 752073150..b06c51dd2 100644 --- a/packages/at_client/lib/src/decryption_service/self_key_decryption.dart +++ b/packages/at_client/lib/src/decryption_service/self_key_decryption.dart @@ -1,4 +1,6 @@ import 'package:at_client/at_client.dart'; +import 'package:at_chops/at_chops.dart'; +import 'package:at_utils/at_logger.dart'; import 'package:at_client/src/decryption_service/decryption.dart'; import 'package:at_client/src/response/default_response_parser.dart'; @@ -9,7 +11,11 @@ import 'package:at_client/src/response/default_response_parser.dart'; /// llookup:@bob:phone@bob class SelfKeyDecryption implements AtKeyDecryption { final AtClient _atClient; - SelfKeyDecryption(this._atClient); + late final AtSignLogger _logger; + SelfKeyDecryption(this._atClient) { + _logger = + AtSignLogger('SelfKeyDecryption (${_atClient.getCurrentAtSign()})'); + } @override Future decrypt(AtKey atKey, dynamic encryptedValue) async { if (encryptedValue == null || @@ -29,8 +35,24 @@ class SelfKeyDecryption implements AtKeyDecryption { exceptionScenario: ExceptionScenario.fetchEncryptionKeys); } - return EncryptionUtil.decryptValue(encryptedValue, - DefaultResponseParser().parse(selfEncryptionKey).response, - ivBase64: atKey.metadata?.ivNonce); + InitialisationVector iV; + if (atKey.metadata?.ivNonce != null) { + iV = AtChopsUtil.generateIVFromBase64String(atKey.metadata!.ivNonce!); + } else { + iV = AtChopsUtil.generateIVLegacy(); + } + AtEncryptionResult decryptionResultFromAtChops; + try { + var encryptionAlgo = AESEncryptionAlgo( + AESKey(DefaultResponseParser().parse(selfEncryptionKey).response)); + decryptionResultFromAtChops = _atClient.atChops!.decryptString( + encryptedValue, EncryptionKeyType.aes256, + encryptionAlgorithm: encryptionAlgo, iv: iV); + } on AtDecryptionException catch (e) { + _logger.severe( + 'decryption exception during of key: ${atKey.key}. Reason: ${e.toString()}'); + rethrow; + } + return decryptionResultFromAtChops.result; } } 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 120fc671c..b59edd03e 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 @@ -5,7 +5,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:meta/meta.dart'; import 'package:at_chops/at_chops.dart'; /// Class responsible for decrypting the value of shared key's that are not owned @@ -14,13 +13,12 @@ import 'package:at_chops/at_chops.dart'; /// CurrentAtSign: @bob /// lookup:phone@alice class SharedKeyDecryption implements AtKeyDecryption { - @visibleForTesting - final AtClient atClient; + final AtClient _atClient; late final AtSignLogger _logger; - SharedKeyDecryption(this.atClient) { + SharedKeyDecryption(this._atClient) { _logger = - AtSignLogger('SharedKeyDecryption (${atClient.getCurrentAtSign()})'); + AtSignLogger('SharedKeyDecryption (${_atClient.getCurrentAtSign()})'); } @override @@ -31,64 +29,75 @@ class SharedKeyDecryption implements AtKeyDecryption { exceptionScenario: ExceptionScenario.decryptionFailed); } String? encryptedSharedKey; - if (atKey.metadata != null && atKey.metadata!.pubKeyCS != null) { + if (atKey.metadata != null) { encryptedSharedKey = atKey.metadata!.sharedKeyEnc; - String? currentAtSignPublicKey; - try { - currentAtSignPublicKey = (await atClient - .getLocalSecondary()! - .getEncryptionPublicKey(atClient.getCurrentAtSign()!)) - ?.trim(); - } on KeyNotFoundException { - throw AtPublicKeyNotFoundException( - 'Failed to fetch the current atSign public key - public:publickey${atClient.getCurrentAtSign()!}', - intent: Intent.fetchEncryptionPublicKey, - exceptionScenario: ExceptionScenario.localVerbExecutionFailed); - } - if (currentAtSignPublicKey != null && - atKey.metadata!.pubKeyCS != - EncryptionUtil.md5CheckSum(currentAtSignPublicKey)) { - throw AtPublicKeyChangeException( - 'Public key has changed. Cannot decrypt shared key ${atKey.toString()}', - intent: Intent.fetchEncryptionPublicKey, - exceptionScenario: ExceptionScenario.encryptionFailed); - } - } else { - encryptedSharedKey = await _getEncryptedSharedKey(atKey); } - if (encryptedSharedKey == null || - encryptedSharedKey.isEmpty || - encryptedSharedKey == 'null') { + encryptedSharedKey ??= await _getEncryptedSharedKey(atKey); + if (encryptedSharedKey.isEmpty || encryptedSharedKey == 'null') { throw SharedKeyNotFoundException('shared encryption key not found', intent: Intent.fetchEncryptionSharedKey, exceptionScenario: ExceptionScenario.fetchEncryptionKeys); } - String decryptedValue = ''; + String? currentAtSignPublicKey; try { - final decryptionResult = atClient.atChops! + currentAtSignPublicKey = (await _atClient + .getLocalSecondary()! + .getEncryptionPublicKey(_atClient.getCurrentAtSign()!)) + ?.trim(); + } on KeyNotFoundException { + throw AtPublicKeyNotFoundException( + 'Failed to fetch the current atSign public key - public:publickey${_atClient.getCurrentAtSign()!}', + intent: Intent.fetchEncryptionPublicKey, + exceptionScenario: ExceptionScenario.localVerbExecutionFailed); + } + if (currentAtSignPublicKey != null && + atKey.metadata!.pubKeyCS != null && + atKey.metadata!.pubKeyCS != + EncryptionUtil.md5CheckSum(currentAtSignPublicKey)) { + throw AtPublicKeyChangeException( + 'Public key has changed. Cannot decrypt shared key ${atKey.toString()}', + intent: Intent.fetchEncryptionPublicKey, + exceptionScenario: ExceptionScenario.encryptionFailed); + } + + AtEncryptionResult decryptionResultFromAtChops; + try { + InitialisationVector iV; + if (atKey.metadata?.ivNonce != null) { + iV = AtChopsUtil.generateIVFromBase64String(atKey.metadata!.ivNonce!); + } else { + iV = AtChopsUtil.generateIVLegacy(); + } + final decryptionResult = _atClient.atChops! .decryptString(encryptedSharedKey, EncryptionKeyType.rsa2048); - decryptedValue = EncryptionUtil.decryptValue( - encryptedValue, decryptionResult.result, - ivBase64: atKey.metadata?.ivNonce); + var encryptionAlgo = AESEncryptionAlgo(AESKey( + DefaultResponseParser().parse(decryptionResult.result).response)); + decryptionResultFromAtChops = _atClient.atChops!.decryptString( + encryptedValue, EncryptionKeyType.aes256, + encryptionAlgorithm: encryptionAlgo, iv: iV); } on AtKeyException catch (e) { e.stack(AtChainedException( Intent.decryptData, ExceptionScenario.decryptionFailed, 'Failed to decrypt ${atKey.toString()}')); rethrow; + } on AtDecryptionException catch (e) { + _logger.severe( + 'decryption exception during of key: ${atKey.key}. Reason: ${e.toString()}'); + rethrow; } - return decryptedValue; + return decryptionResultFromAtChops.result; } Future _getEncryptedSharedKey(AtKey atKey) async { String? encryptedSharedKey = ''; var localLookupSharedKeyBuilder = LLookupVerbBuilder() ..atKey = AtConstants.atEncryptionSharedKey - ..sharedWith = atClient.getCurrentAtSign() + ..sharedWith = _atClient.getCurrentAtSign() ..sharedBy = atKey.sharedBy ..isCached = true; try { - encryptedSharedKey = await atClient + encryptedSharedKey = await _atClient .getLocalSecondary()! .executeVerb(localLookupSharedKeyBuilder); } on KeyNotFoundException { @@ -102,7 +111,7 @@ class SharedKeyDecryption implements AtKeyDecryption { ..atKey = AtConstants.atEncryptionSharedKey ..sharedBy = atKey.sharedBy ..auth = true; - encryptedSharedKey = await atClient + encryptedSharedKey = await _atClient .getRemoteSecondary()! .executeVerb(sharedKeyLookUpBuilder); encryptedSharedKey = diff --git a/packages/at_client/lib/src/util/encryption_util.dart b/packages/at_client/lib/src/util/encryption_util.dart index 5bfd39abb..d00cc9cb9 100644 --- a/packages/at_client/lib/src/util/encryption_util.dart +++ b/packages/at_client/lib/src/util/encryption_util.dart @@ -7,6 +7,8 @@ import 'package:encrypt/encrypt.dart'; import 'package:crypto/crypto.dart'; import 'package:at_utils/at_logger.dart'; +//#TODO Replace calls to methods in this class with at_chops methods and +// move this class to test folder in next major release class EncryptionUtil { static final _logger = AtSignLogger('EncryptionUtil'); diff --git a/packages/at_client/test/decryption_service_test.dart b/packages/at_client/test/decryption_service_test.dart index f0a1e1203..2a1bd08af 100644 --- a/packages/at_client/test/decryption_service_test.dart +++ b/packages/at_client/test/decryption_service_test.dart @@ -25,6 +25,8 @@ class MockGetRequestTransformer extends Mock implements GetRequestTransformer {} class MockSecondaryManager extends Mock implements SecondaryManager {} +class FakeLocalLookUpVerbBuilder extends Fake implements LLookupVerbBuilder {} + void main() { AtLookupImpl mockAtLookup = MockAtLookup(); AtClientImpl mockAtClientImpl = MockAtClientImpl(); @@ -35,12 +37,9 @@ void main() { ..atKey = 'phone.wavi' ..sharedBy = '@alice'; - var llookupVerbBuilder = LLookupVerbBuilder() - ..atKey = 'shared_key.sitaram' - ..sharedBy = '@murali'; - setUp(() { reset(mockAtLookup); + registerFallbackValue(FakeLocalLookUpVerbBuilder()); when(() => mockAtLookup.executeVerb(lookupVerbBuilder)).thenAnswer( (_) async => throw AtExceptionUtils.get('AT0015', 'Connection timeout')); @@ -49,7 +48,7 @@ void main() { when(() => mockAtClientImpl.getCurrentAtSign()).thenAnswer((_) => '@xyz'); when(() => mockLocalSecondary.getEncryptionPublicKey('@xyz')) .thenAnswer((_) => Future.value('dummy_encryption_public_key')); - when(() => mockLocalSecondary.executeVerb(llookupVerbBuilder)) + when(() => mockLocalSecondary.executeVerb(any())) .thenAnswer((_) async => 'dummy_shared_key'); when(() => mockAtClientImpl.atChops).thenAnswer((_) => mockAtChops); }); @@ -89,33 +88,6 @@ void main() { }); group('A group of tests to verify exceptions in decryption service', () { - test( - 'A test to verify exception is thrown when public key checksum changes', - () { - var atKey = (AtKey.shared('phone', namespace: 'wavi', sharedBy: '@murali') - ..sharedWith('@sitaram')) - .build(); - atKey.metadata = Metadata()..pubKeyCS = '1234'; - var sharedKeyDecryption = SharedKeyDecryption(mockAtClientImpl); - expect(() => sharedKeyDecryption.decrypt(atKey, '123'), - throwsA(predicate((dynamic e) => e is AtPublicKeyChangeException))); - }); - - test('A test to verify exception is thrown when shared key is not found', - () { - var atKey = (AtKey.shared('phone', namespace: 'wavi', sharedBy: '@murali') - ..sharedWith('@sitaram')) - .build(); - atKey.metadata = Metadata() - ..pubKeyCS = 'd4f6d9483907286a0563b9fdeb01aa61'; - var sharedKeyDecryption = SharedKeyDecryption(mockAtClientImpl); - expect( - () => sharedKeyDecryption.decrypt(atKey, '123'), - throwsA(predicate((dynamic e) => - e is SharedKeyNotFoundException && - e.message == 'shared encryption key not found'))); - }); - test( 'A test to verify exception is thrown when current atsign public key is not found', () { diff --git a/packages/at_client/test/local_key_decryption_test.dart b/packages/at_client/test/local_key_decryption_test.dart index 2c2f06036..28ec39925 100644 --- a/packages/at_client/test/local_key_decryption_test.dart +++ b/packages/at_client/test/local_key_decryption_test.dart @@ -39,7 +39,6 @@ void main() { sharedSymmetricKey, rsaKeyPair.atPublicKey.publicKey); AtChopsKeys atChopsKeys = AtChopsKeys.create(rsaKeyPair, null); var atChopsImpl = AtChopsImpl(atChopsKeys); - mockAtClient.atChops = atChopsImpl; when(() => mockAtClient.atChops).thenAnswer((_) => atChopsImpl); print('encryptedSharedSymmetricKey:$encryptedSharedSymmetricKey'); when(() => mockAtClient.getLocalSecondary()) diff --git a/packages/at_client/test/self_key_decryption_test.dart b/packages/at_client/test/self_key_decryption_test.dart new file mode 100644 index 000000000..dd83e96a9 --- /dev/null +++ b/packages/at_client/test/self_key_decryption_test.dart @@ -0,0 +1,112 @@ +import 'package:at_chops/at_chops.dart'; +import 'package:at_client/src/decryption_service/self_key_decryption.dart'; +import 'package:at_commons/at_builders.dart'; +import 'package:test/test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:at_client/at_client.dart'; +import 'package:at_lookup/at_lookup.dart'; + +class MockAtClientImpl extends Mock implements AtClient {} + +class MockLocalSecondary extends Mock implements LocalSecondary {} + +class MockRemoteSecondary extends Mock implements RemoteSecondary {} + +class MockAtLookupImpl extends Mock implements AtLookUp {} + +class FakeLocalLookUpVerbBuilder extends Fake implements LLookupVerbBuilder {} + +class FakeDeleteVerbBuilder extends Fake implements DeleteVerbBuilder {} + +void main() { + AtClient mockAtClient = MockAtClientImpl(); + AtLookUp mockAtLookUp = MockAtLookupImpl(); + LocalSecondary mockLocalSecondary = MockLocalSecondary(); + setUp(() { + reset(mockAtLookUp); + when(() => mockAtClient.getLocalSecondary()) + .thenAnswer((_) => mockLocalSecondary); + + registerFallbackValue(FakeLocalLookUpVerbBuilder()); + registerFallbackValue(FakeDeleteVerbBuilder()); + }); + + test('test to check AtDecryptionException when encrypted value is null', + () async { + var selfKeyDecryption = SelfKeyDecryption(mockAtClient); + var selfKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@alice' + ..key = 'location'; + expect( + () async => await selfKeyDecryption.decrypt(selfKey, null), + throwsA(predicate((e) => + e is AtDecryptionException && + e.message == 'Decryption failed. Encrypted value is null'))); + }); + + test( + 'test to check SelfKeyNotFoundException when self key is not found in local secondary', + () async { + var selfKeyDecryption = SelfKeyDecryption(mockAtClient); + when(() => mockLocalSecondary.getEncryptionSelfKey()) + .thenAnswer((_) => Future.value(null)); + var selfKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@alice' + ..key = 'location'; + expect( + () async => await selfKeyDecryption.decrypt(selfKey, 'a@#!!c'), + throwsA(predicate((e) => + e is SelfKeyNotFoundException && + e.message == 'Empty or null SelfEncryptionKey found'))); + }); + + test('test to check self encryption key decrypt method without IV', () async { + AtChopsKeys atChopsKeys = AtChopsKeys.create(null, null); + var atChopsImpl = AtChopsImpl(atChopsKeys); + when(() => mockAtClient.atChops).thenAnswer((_) => atChopsImpl); + var selfKeyDecryption = SelfKeyDecryption(mockAtClient); + SymmetricKey selfEncryptionKey = + AtChopsUtil.generateSymmetricKey(EncryptionKeyType.aes256); + var location = 'san francisco'; + var encryptedLocation = + EncryptionUtil.encryptValue(location, selfEncryptionKey.key); + + when(() => mockLocalSecondary.getEncryptionSelfKey()) + .thenAnswer((_) => Future.value(selfEncryptionKey.key)); + var selfKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@alice' + ..key = 'location'; + var decryptedValue = + await selfKeyDecryption.decrypt(selfKey, encryptedLocation); + expect(decryptedValue, location); + }); + + test('test to check self encryption key decrypt method with IV', () async { + AtChopsKeys atChopsKeys = AtChopsKeys.create(null, null); + var atChopsImpl = AtChopsImpl(atChopsKeys); + when(() => mockAtClient.atChops).thenAnswer((_) => atChopsImpl); + var selfKeyDecryption = SelfKeyDecryption(mockAtClient); + SymmetricKey selfEncryptionKey = + AtChopsUtil.generateSymmetricKey(EncryptionKeyType.aes256); + var location = 'new york'; + var ivBase64String = 'YmFzZTY0IGVuY29kaW5n'; + + var encryptedLocation = EncryptionUtil.encryptValue( + location, selfEncryptionKey.key, + ivBase64: ivBase64String); + + when(() => mockLocalSecondary.getEncryptionSelfKey()) + .thenAnswer((_) => Future.value(selfEncryptionKey.key)); + var selfKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@alice' + ..key = 'location'; + selfKey.metadata = Metadata()..ivNonce = ivBase64String; + var decryptedValue = + await selfKeyDecryption.decrypt(selfKey, encryptedLocation); + expect(decryptedValue, location); + }); +} diff --git a/packages/at_client/test/shared_key_decryption_test.dart b/packages/at_client/test/shared_key_decryption_test.dart new file mode 100644 index 000000000..8589dbd45 --- /dev/null +++ b/packages/at_client/test/shared_key_decryption_test.dart @@ -0,0 +1,216 @@ +import 'package:at_chops/at_chops.dart'; +import 'package:at_client/src/decryption_service/self_key_decryption.dart'; +import 'package:at_client/src/decryption_service/shared_key_decryption.dart'; +import 'package:at_commons/at_builders.dart'; +import 'package:test/test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:at_client/at_client.dart'; +import 'package:at_lookup/at_lookup.dart'; + +class MockAtClientImpl extends Mock implements AtClient {} + +class MockLocalSecondary extends Mock implements LocalSecondary {} + +class MockRemoteSecondary extends Mock implements RemoteSecondary {} + +class MockAtLookupImpl extends Mock implements AtLookUp {} + +class FakeLocalLookUpVerbBuilder extends Fake implements LLookupVerbBuilder {} + +class FakeDeleteVerbBuilder extends Fake implements DeleteVerbBuilder {} + +void main() { + AtClient mockAtClient = MockAtClientImpl(); + AtLookUp mockAtLookUp = MockAtLookupImpl(); + LocalSecondary mockLocalSecondary = MockLocalSecondary(); + RemoteSecondary mockRemoteSecondary = MockRemoteSecondary(); + setUp(() { + reset(mockAtLookUp); + when(() => mockAtClient.getLocalSecondary()) + .thenAnswer((_) => mockLocalSecondary); + when(() => mockAtClient.getRemoteSecondary()) + .thenAnswer((_) => mockRemoteSecondary); + + registerFallbackValue(FakeLocalLookUpVerbBuilder()); + registerFallbackValue(FakeDeleteVerbBuilder()); + }); + + test('test to check AtDecryptionException when encrypted value is null', + () async { + var sharedKeyDecryption = SharedKeyDecryption(mockAtClient); + var sharedKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@bob' + ..key = 'location'; + expect( + () async => await sharedKeyDecryption.decrypt(sharedKey, null), + throwsA(predicate((e) => + e is AtDecryptionException && + e.message == 'Decryption failed. Encrypted value is null'))); + }); + + test('test to check SharedKeyNotFound exception', () async { + var sharedKeyDecryption = SharedKeyDecryption(mockAtClient); + when(() => mockAtClient.getCurrentAtSign()).thenReturn('@alice'); + when(() => mockLocalSecondary.getEncryptionPublicKey('@alice')) + .thenAnswer((_) => Future.value(null)); + when(() => mockLocalSecondary.executeVerb(any())) + .thenAnswer((_) => Future.value('data:null')); + when(() => mockRemoteSecondary.executeVerb(any())) + .thenAnswer((_) => Future.value('data:null')); + var sharedKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@bob' + ..key = 'location'; + sharedKey.metadata = (Metadata()..pubKeyCS = 'testCheckSum'); + expect( + () async => await sharedKeyDecryption.decrypt(sharedKey, 'a@#!!c'), + throwsA(predicate((e) => + e is SharedKeyNotFoundException && + e.message == 'shared encryption key not found'))); + }); + + test( + 'test to check AtPublicKeyNotFoundException - public key is not found in local secondary', + () async { + var sharedKeyDecryption = SharedKeyDecryption(mockAtClient); + when(() => mockAtClient.getCurrentAtSign()).thenReturn('@alice'); + when(() => + mockLocalSecondary.getEncryptionPublicKey( + '@alice')).thenThrow(AtPublicKeyNotFoundException( + 'Failed to fetch the current atSign public key - public:publickey@alice')); + when(() => mockLocalSecondary.executeVerb(any())) + .thenAnswer((_) => Future.value('data:testEncryptedSharedKey')); + var sharedKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@bob' + ..key = 'location'; + sharedKey.metadata = (Metadata()..pubKeyCS = 'testCheckSum'); + expect( + () async => await sharedKeyDecryption.decrypt(sharedKey, 'a@#!!c'), + throwsA(predicate((e) => + e is AtPublicKeyNotFoundException && + e.message == + 'Failed to fetch the current atSign public key - public:publickey@alice'))); + }); + + test( + 'test to check AtPublicKeyChangeException - checksum from metadata is different from checksum of retrieved public key', + () async { + var sharedKeyDecryption = SharedKeyDecryption(mockAtClient); + when(() => mockAtClient.getCurrentAtSign()).thenReturn('@alice'); + when(() => mockLocalSecondary.getEncryptionPublicKey('@alice')) + .thenAnswer((_) => Future.value('testPublicKey')); + when(() => mockLocalSecondary.executeVerb(any())) + .thenAnswer((_) => Future.value('data:testEncryptedSharedKey')); + var sharedKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@bob' + ..key = 'location'; + sharedKey.metadata = (Metadata()..pubKeyCS = 'testCheckSum'); + expect( + () async => await sharedKeyDecryption.decrypt(sharedKey, 'a@#!!c'), + throwsA(predicate((e) => + e is AtPublicKeyChangeException && + e.message == + 'Public key has changed. Cannot decrypt shared key @bob:location@alice'))); + }); + + test('test to check shared key decryption - without IV', () async { + var sharedKeyDecryption = SharedKeyDecryption(mockAtClient); + var aliceEncryptionPublicKey = + 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0sYjWt6TTikajY3HjvdN3sn2Ve3O+i84/gBrWPqhGNSdImz3W2l9dMSHm4wyixsxMSQaL+rECjwnvp3sRdW3M51sDCvWa06MLptvdrtnMjzDrvP45hUJY/i6WeDW8qeEOf9zuo+BLcQ3pkV1KZyhBj80OndLS/y00T8fYB9KnS5Z/iN7KW7Hxuv0isMPXxL1i8AZos7m5GuWq7CfRFKJIZ6vqYBUJCVSQCUVo1llyjElodSywcf1KjCvBOKuMPnUQCs+pKJt3QMFI0U7D+yinnlEdr6TBfOzMMPS3Du1LHpTGt7rqyxZrX8p4kpVb/CyL6wkelMuahHDOeNFBNyF0wIDAQAB'; + var aliceEncryptionPrivateKey = + 'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDSxiNa3pNOKRqNjceO903eyfZV7c76Lzj+AGtY+qEY1J0ibPdbaX10xIebjDKLGzExJBov6sQKPCe+nexF1bcznWwMK9ZrTowum292u2cyPMOu8/jmFQlj+LpZ4Nbyp4Q5/3O6j4EtxDemRXUpnKEGPzQ6d0tL/LTRPx9gH0qdLln+I3spbsfG6/SKww9fEvWLwBmizubka5arsJ9EUokhnq+pgFQkJVJAJRWjWWXKMSWh1LLBx/UqMK8E4q4w+dRAKz6kom3dAwUjRTsP7KKeeUR2vpMF87Mww9LcO7UselMa3uurLFmtfyniSlVv8LIvrCR6Uy5qEcM540UE3IXTAgMBAAECggEAfImMLE3k1cLdqJQEPIoNHb1RatZXfGXYk+QliW6VLzm5GrUttnpvIUZaJeNBngXUHAgL3RInATEn/q4LA/xSAhJa3Bou2DqSA5vd0VbLk9hpev82qqP1Z3d4jFCYUMoAC9DPTYUrO6J7iyfxIUQltK41qvH/sIdBQ327iS0UBihhiKg16BOKG4SoFJHZfhhL6m86+jnsaBTaAWb8hpa/Mwqs5eDHF78DHK8o+4Q6DufDi34nCwdxEexL3MFa9L0qGbQAqJshgDcJ6yxUzb5+tw3XXpiE0yG9aZ5gPaS2UgOYY1m2mmF4RjFSiLmKyN99H99ycA59enVFyfYh4SnuMQKBgQDq9IwkVyDkNxkaW6hyYMzBwNqId74JUNjXCWyzDJ58JWaNvFYYY4ujSCLTdfdugmVTIUjqXMVsxzq/e9jNaOj7u27/3inqn1VC88GFJJiUiLQcTP1T5ySP4jy5GVrhQ1zP8PtiRqE34emYfVY8OLa7bwf5CufgbL5RzKPrfIafKQKBgQDlpx8DoETRPE7FyZJg9xiUTyZmI/P6RmhCO86knbQa4hEWiCuEIiOheJQxdcW6yCNImbJNSEFUnpweiHEw4xdMmlpR4JDkvsGOyjLI6Y36Yxbi+AipvTuYZ/La7fuOeEjwD7OlgJmva2jEQL6GlhmTibgt5dfwzOiAP0gC4tXomwKBgQDAnZDSLfeSADV9LU0vz3mtEYxWOkw52OSbjWdmdd7ricHESnUOc3VDe9zJHLmnCBFHEE91im5zWfUoi8BVzT7LOIKsEpaseMjuJWUt4K2Rf2ygkuFPSnvn1SHQ4R9m8tGAy19a1upOJM9bKs1qe1ga2tBfc3havOtdpfVwFVtL2QKBgDFsPehx3V2KNQmrz6y+gLOqNQFWS3NZI6bdaCNVLSV78WF//J17G1/sqzfZuKvx1mYRbaXkHusvFzoa8wEqXiFGNpnYUlZoFw+7xCIo4T05hftilbqx1tl9xW4IOVL33/qJ5od/nZN68hkKNfaQ5wAxa0m1ZTuVXZP8CmtUleRxAoGAQcz+GcrzLybY8EMOhYRbb79H8tQ+SoFkTOmJV4ZQDxzg18dPd8+U3YaMp3QngxncOSpjSwsuqpw/SqxHBQE/v91uEzLfPPS2QJ5COBvXM2Y7PsSmMnukIOM0NrtU8MIonv+l7UsHDeCllqe5+uRPpBUUk2mljPVprXo0SDjQr1U='; + var aesSharedKey = '7l00PvmXMD9i1z0Q72O7RNQc6D9/9k9FrqfvCZcEBqs='; + var encryptedSharedKey = + EncryptionUtil.encryptKey(aesSharedKey, aliceEncryptionPublicKey); + var publicKeyCheckSum = + EncryptionUtil.md5CheckSum(aliceEncryptionPublicKey); + var location = 'California'; + var encryptedLocation = EncryptionUtil.encryptValue(location, aesSharedKey); + var atEncryptionKeyPair = AtEncryptionKeyPair.create( + aliceEncryptionPublicKey, aliceEncryptionPrivateKey); + + AtChopsKeys atChopsKeys = AtChopsKeys.create(atEncryptionKeyPair, null); + var atChopsImpl = AtChopsImpl(atChopsKeys); + when(() => mockAtClient.atChops).thenAnswer((_) => atChopsImpl); + when(() => mockAtClient.getCurrentAtSign()).thenReturn('@alice'); + when(() => mockLocalSecondary.getEncryptionPublicKey('@alice')) + .thenAnswer((_) => Future.value(aliceEncryptionPublicKey)); + when(() => mockLocalSecondary.executeVerb(any())) + .thenAnswer((_) => Future.value('data:$encryptedSharedKey')); + var sharedKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@bob' + ..key = 'location'; + sharedKey.metadata = (Metadata()..pubKeyCS = publicKeyCheckSum); + var decryptedLocation = + await sharedKeyDecryption.decrypt(sharedKey, encryptedLocation); + expect(decryptedLocation, location); + }); + + test('test to check shared key decryption - with IV', () async { + var sharedKeyDecryption = SharedKeyDecryption(mockAtClient); + var aliceEncryptionPublicKey = + 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0sYjWt6TTikajY3HjvdN3sn2Ve3O+i84/gBrWPqhGNSdImz3W2l9dMSHm4wyixsxMSQaL+rECjwnvp3sRdW3M51sDCvWa06MLptvdrtnMjzDrvP45hUJY/i6WeDW8qeEOf9zuo+BLcQ3pkV1KZyhBj80OndLS/y00T8fYB9KnS5Z/iN7KW7Hxuv0isMPXxL1i8AZos7m5GuWq7CfRFKJIZ6vqYBUJCVSQCUVo1llyjElodSywcf1KjCvBOKuMPnUQCs+pKJt3QMFI0U7D+yinnlEdr6TBfOzMMPS3Du1LHpTGt7rqyxZrX8p4kpVb/CyL6wkelMuahHDOeNFBNyF0wIDAQAB'; + var aliceEncryptionPrivateKey = + 'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDSxiNa3pNOKRqNjceO903eyfZV7c76Lzj+AGtY+qEY1J0ibPdbaX10xIebjDKLGzExJBov6sQKPCe+nexF1bcznWwMK9ZrTowum292u2cyPMOu8/jmFQlj+LpZ4Nbyp4Q5/3O6j4EtxDemRXUpnKEGPzQ6d0tL/LTRPx9gH0qdLln+I3spbsfG6/SKww9fEvWLwBmizubka5arsJ9EUokhnq+pgFQkJVJAJRWjWWXKMSWh1LLBx/UqMK8E4q4w+dRAKz6kom3dAwUjRTsP7KKeeUR2vpMF87Mww9LcO7UselMa3uurLFmtfyniSlVv8LIvrCR6Uy5qEcM540UE3IXTAgMBAAECggEAfImMLE3k1cLdqJQEPIoNHb1RatZXfGXYk+QliW6VLzm5GrUttnpvIUZaJeNBngXUHAgL3RInATEn/q4LA/xSAhJa3Bou2DqSA5vd0VbLk9hpev82qqP1Z3d4jFCYUMoAC9DPTYUrO6J7iyfxIUQltK41qvH/sIdBQ327iS0UBihhiKg16BOKG4SoFJHZfhhL6m86+jnsaBTaAWb8hpa/Mwqs5eDHF78DHK8o+4Q6DufDi34nCwdxEexL3MFa9L0qGbQAqJshgDcJ6yxUzb5+tw3XXpiE0yG9aZ5gPaS2UgOYY1m2mmF4RjFSiLmKyN99H99ycA59enVFyfYh4SnuMQKBgQDq9IwkVyDkNxkaW6hyYMzBwNqId74JUNjXCWyzDJ58JWaNvFYYY4ujSCLTdfdugmVTIUjqXMVsxzq/e9jNaOj7u27/3inqn1VC88GFJJiUiLQcTP1T5ySP4jy5GVrhQ1zP8PtiRqE34emYfVY8OLa7bwf5CufgbL5RzKPrfIafKQKBgQDlpx8DoETRPE7FyZJg9xiUTyZmI/P6RmhCO86knbQa4hEWiCuEIiOheJQxdcW6yCNImbJNSEFUnpweiHEw4xdMmlpR4JDkvsGOyjLI6Y36Yxbi+AipvTuYZ/La7fuOeEjwD7OlgJmva2jEQL6GlhmTibgt5dfwzOiAP0gC4tXomwKBgQDAnZDSLfeSADV9LU0vz3mtEYxWOkw52OSbjWdmdd7ricHESnUOc3VDe9zJHLmnCBFHEE91im5zWfUoi8BVzT7LOIKsEpaseMjuJWUt4K2Rf2ygkuFPSnvn1SHQ4R9m8tGAy19a1upOJM9bKs1qe1ga2tBfc3havOtdpfVwFVtL2QKBgDFsPehx3V2KNQmrz6y+gLOqNQFWS3NZI6bdaCNVLSV78WF//J17G1/sqzfZuKvx1mYRbaXkHusvFzoa8wEqXiFGNpnYUlZoFw+7xCIo4T05hftilbqx1tl9xW4IOVL33/qJ5od/nZN68hkKNfaQ5wAxa0m1ZTuVXZP8CmtUleRxAoGAQcz+GcrzLybY8EMOhYRbb79H8tQ+SoFkTOmJV4ZQDxzg18dPd8+U3YaMp3QngxncOSpjSwsuqpw/SqxHBQE/v91uEzLfPPS2QJ5COBvXM2Y7PsSmMnukIOM0NrtU8MIonv+l7UsHDeCllqe5+uRPpBUUk2mljPVprXo0SDjQr1U='; + var aesSharedKey = '7l00PvmXMD9i1z0Q72O7RNQc6D9/9k9FrqfvCZcEBqs='; + var encryptedSharedKey = + EncryptionUtil.encryptKey(aesSharedKey, aliceEncryptionPublicKey); + var publicKeyCheckSum = + EncryptionUtil.md5CheckSum(aliceEncryptionPublicKey); + var location = 'California'; + var ivBase64String = 'YmFzZTY0IGVuY29kaW5n'; + var encryptedLocation = EncryptionUtil.encryptValue(location, aesSharedKey, + ivBase64: ivBase64String); + var atEncryptionKeyPair = AtEncryptionKeyPair.create( + aliceEncryptionPublicKey, aliceEncryptionPrivateKey); + + AtChopsKeys atChopsKeys = AtChopsKeys.create(atEncryptionKeyPair, null); + var atChopsImpl = AtChopsImpl(atChopsKeys); + when(() => mockAtClient.atChops).thenAnswer((_) => atChopsImpl); + when(() => mockAtClient.getCurrentAtSign()).thenReturn('@alice'); + when(() => mockLocalSecondary.getEncryptionPublicKey('@alice')) + .thenAnswer((_) => Future.value(aliceEncryptionPublicKey)); + when(() => mockLocalSecondary.executeVerb(any())) + .thenAnswer((_) => Future.value('data:$encryptedSharedKey')); + var sharedKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@bob' + ..key = 'location'; + sharedKey.metadata = (Metadata() + ..pubKeyCS = publicKeyCheckSum + ..ivNonce = ivBase64String); + var decryptedLocation = + await sharedKeyDecryption.decrypt(sharedKey, encryptedLocation); + expect(decryptedLocation, location); + }); + + test('test to check shared key decryption with IV', () async { + AtChopsKeys atChopsKeys = AtChopsKeys.create(null, null); + var atChopsImpl = AtChopsImpl(atChopsKeys); + when(() => mockAtClient.atChops).thenAnswer((_) => atChopsImpl); + var selfKeyDecryption = SelfKeyDecryption(mockAtClient); + SymmetricKey selfEncryptionKey = + AtChopsUtil.generateSymmetricKey(EncryptionKeyType.aes256); + var location = 'new york'; + var ivBase64String = 'YmFzZTY0IGVuY29kaW5n'; + + var encryptedLocation = EncryptionUtil.encryptValue( + location, selfEncryptionKey.key, + ivBase64: ivBase64String); + + when(() => mockLocalSecondary.getEncryptionSelfKey()) + .thenAnswer((_) => Future.value(selfEncryptionKey.key)); + var selfKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@alice' + ..key = 'location'; + selfKey.metadata = Metadata()..ivNonce = ivBase64String; + var decryptedValue = + await selfKeyDecryption.decrypt(selfKey, encryptedLocation); + expect(decryptedValue, location); + }); +} From 5d898d4783903f787bf91994552255b6dbd94815 Mon Sep 17 00:00:00 2001 From: Murali Date: Tue, 19 Dec 2023 17:06:05 +0530 Subject: [PATCH 4/5] fix: end2end test failure --- .../lib/src/decryption_service/shared_key_decryption.dart | 1 + 1 file changed, 1 insertion(+) 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 b59edd03e..7ead5cbd3 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 @@ -51,6 +51,7 @@ class SharedKeyDecryption implements AtKeyDecryption { exceptionScenario: ExceptionScenario.localVerbExecutionFailed); } if (currentAtSignPublicKey != null && + atKey.metadata != null && atKey.metadata!.pubKeyCS != null && atKey.metadata!.pubKeyCS != EncryptionUtil.md5CheckSum(currentAtSignPublicKey)) { From 4d5348f111a54c585fe0af2117594ef888b34170 Mon Sep 17 00:00:00 2001 From: Murali Date: Wed, 3 Jan 2024 13:36:43 +0530 Subject: [PATCH 5/5] fix: review comments --- .../local_key_decryption.dart | 7 ++-- .../shared_key_decryption.dart | 2 +- .../abstract_atkey_encryption.dart | 40 +++++++++---------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/packages/at_client/lib/src/decryption_service/local_key_decryption.dart b/packages/at_client/lib/src/decryption_service/local_key_decryption.dart index 84b318672..bec3cdbd7 100644 --- a/packages/at_client/lib/src/decryption_service/local_key_decryption.dart +++ b/packages/at_client/lib/src/decryption_service/local_key_decryption.dart @@ -11,10 +11,11 @@ import 'package:at_utils/at_logger.dart'; class LocalKeyDecryption extends AbstractAtKeyEncryption implements AtKeyDecryption { late final AtSignLogger _logger; + final AtClient _atClient; - LocalKeyDecryption(AtClient atClient) : super(atClient) { + LocalKeyDecryption(this._atClient) : super(_atClient) { _logger = - AtSignLogger('LocalKeyDecryption (${atClient.getCurrentAtSign()})'); + AtSignLogger('LocalKeyDecryption (${_atClient.getCurrentAtSign()})'); } @override @@ -42,7 +43,7 @@ class LocalKeyDecryption extends AbstractAtKeyEncryption AtEncryptionResult decryptionResultFromAtChops; try { var encryptionAlgo = AESEncryptionAlgo(AESKey(symmetricKey)); - decryptionResultFromAtChops = super.atClient.atChops!.decryptString( + decryptionResultFromAtChops = _atClient.atChops!.decryptString( encryptedValue, EncryptionKeyType.aes256, encryptionAlgorithm: encryptionAlgo, iv: iV); _logger.finer( 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 7ead5cbd3..4e2d541d0 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 @@ -58,7 +58,7 @@ class SharedKeyDecryption implements AtKeyDecryption { throw AtPublicKeyChangeException( 'Public key has changed. Cannot decrypt shared key ${atKey.toString()}', intent: Intent.fetchEncryptionPublicKey, - exceptionScenario: ExceptionScenario.encryptionFailed); + 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 3d8434874..85d581f23 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 @@ -15,16 +15,16 @@ import 'package:at_chops/at_chops.dart'; abstract class AbstractAtKeyEncryption implements AtKeyEncryption { late final AtSignLogger _logger; late String _sharedKey; - final AtClient atClient; + final AtClient _atClient; AtCommitLog? atCommitLog; DefaultResponseParser defaultResponseParser = DefaultResponseParser(); String get sharedKey => _sharedKey; - AbstractAtKeyEncryption(this.atClient) { + AbstractAtKeyEncryption(this._atClient) { _logger = AtSignLogger( - 'AbstractAtKeyEncryption (${atClient.getCurrentAtSign()})'); + 'AbstractAtKeyEncryption (${_atClient.getCurrentAtSign()})'); } SyncUtil syncUtil = SyncUtil(); @@ -68,7 +68,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { try { /// Look first in local storage encryptedSharedKey = await _getMyEncryptedCopyOfSharedSymmetricKey( - atClient.getLocalSecondary()!, atKey); + _atClient.getLocalSecondary()!, atKey); } on KeyNotFoundException { encryptedSharedKey = null; } @@ -81,17 +81,17 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { // Defensive code to ensure that 'their' copy is not in local storage // if 'our' copy is not in local storage await deleteTheirCopyOfEncryptedSharedKey( - atKey, atClient.getLocalSecondary()!); + atKey, _atClient.getLocalSecondary()!); _logger.info( 'Fetching shared symmetric key for ${atKey.sharedBy} from atServer'); encryptedSharedKey = await _getMyEncryptedCopyOfSharedSymmetricKey( - atClient.getRemoteSecondary()!, atKey); + _atClient.getRemoteSecondary()!, atKey); if (encryptedSharedKey != null && encryptedSharedKey != 'data:null') { // If found on atServer, save to local _logger.info( 'Retrieved my encrypted copy of shared symmetric key for ${atKey.sharedWith} from atServer - saving to local storage'); await _storeMyEncryptedCopyOfSharedSymmetricKey( - atKey, encryptedSharedKey, atClient.getLocalSecondary()!); + atKey, encryptedSharedKey, _atClient.getLocalSecondary()!); } } } on KeyNotFoundException { @@ -107,7 +107,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { /// - If found existing in either local or atServer, decrypt it and return encryptedSharedKey = defaultResponseParser.parse(encryptedSharedKey!).response; - final decryptionResult = atClient.atChops! + final decryptionResult = _atClient.atChops! .decryptString(encryptedSharedKey, EncryptionKeyType.rsa2048); return decryptionResult.result; } @@ -124,7 +124,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { // Fetch our encryption public key String? currentAtSignEncryptionPublicKey; try { - currentAtSignEncryptionPublicKey = await atClient + currentAtSignEncryptionPublicKey = await _atClient .getLocalSecondary()! .getEncryptionPublicKey(atKey.sharedBy!); } on KeyNotFoundException catch (e) { @@ -143,14 +143,14 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { // Defensive code to ensure that we do not have an old 'their' copy on atServer await deleteTheirCopyOfEncryptedSharedKey( - atKey, atClient.getRemoteSecondary()!); + atKey, _atClient.getRemoteSecondary()!); // Store my copy for future use // First, store to atServer // try { _logger.info("Storing new shared symmetric key to atServer"); await _storeMyEncryptedCopyOfSharedSymmetricKey( - atKey, encryptedSharedKeyMyCopy, atClient.getRemoteSecondary()!); + atKey, encryptedSharedKeyMyCopy, _atClient.getRemoteSecondary()!); // // TODO // } on KeyAlreadyExistsException catch (e) { // return await getMyCopyOfSharedSymmetricKey(atKey); @@ -159,7 +159,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { // Now store to local _logger.info("Storing new shared symmetric key to local storage"); await _storeMyEncryptedCopyOfSharedSymmetricKey( - atKey, encryptedSharedKeyMyCopy, atClient.getLocalSecondary()!); + atKey, encryptedSharedKeyMyCopy, _atClient.getLocalSecondary()!); // Return the unencrypted symmetric key return newSymmetricKeyBase64; @@ -178,7 +178,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { /// - Check if encrypted copy exists in local storage String? theirEncryptedCopy = await _getTheirEncryptedCopyOfSharedSymmetricKey( - atClient.getLocalSecondary()!, atKey); + _atClient.getLocalSecondary()!, atKey); // Found it in local storage. Return it. if (theirEncryptedCopy != null) { return theirEncryptedCopy; @@ -188,7 +188,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { _logger.info("'Their' copy of shared symmetric key for ${atKey.sharedWith}" " not found in local storage - will check atServer"); theirEncryptedCopy = await _getTheirEncryptedCopyOfSharedSymmetricKey( - atClient.getRemoteSecondary()!, atKey); + _atClient.getRemoteSecondary()!, atKey); /// - If in atServer, save to local storage and return if (theirEncryptedCopy != null) { @@ -197,7 +197,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { " in atServer - saving to local storage"); await storeTheirCopyOfEncryptedSharedKeyToSecondary( atKey, theirEncryptedCopy, - secondary: atClient.getLocalSecondary()!); + secondary: _atClient.getLocalSecondary()!); return theirEncryptedCopy; } @@ -229,14 +229,14 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { "Saving 'their' copy of shared symmetric key for ${atKey.sharedWith} to atServer"); await storeTheirCopyOfEncryptedSharedKeyToSecondary( atKey, theirEncryptedCopy, - secondary: atClient.getRemoteSecondary()!); + secondary: _atClient.getRemoteSecondary()!); /// - (c) save encrypted copy to local storage and return _logger.info( "Saving 'their' copy of shared symmetric key for ${atKey.sharedWith} to local storage"); await storeTheirCopyOfEncryptedSharedKeyToSecondary( atKey, theirEncryptedCopy, - secondary: atClient.getLocalSecondary()!); + secondary: _atClient.getLocalSecondary()!); return theirEncryptedCopy; } @@ -253,7 +253,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { ..isPublic = true ..isCached = true; - sharedWithPublicKey = await atClient + sharedWithPublicKey = await _atClient .getLocalSecondary()! .executeVerb(cachedEncryptionPublicKeyBuilder); } on KeyNotFoundException { @@ -264,7 +264,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { var encryptionPublicKeyBuilder = PLookupVerbBuilder() ..atKey = 'publickey' ..sharedBy = atKey.sharedWith; - sharedWithPublicKey = await atClient + sharedWithPublicKey = await _atClient .getRemoteSecondary()! .executeVerb(encryptionPublicKeyBuilder); } @@ -344,7 +344,7 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { Future storeTheirCopyOfEncryptedSharedKeyToSecondary( AtKey atKey, String encryptedSharedKeyValue, {Secondary? secondary}) async { - secondary ??= atClient.getLocalSecondary()!; + secondary ??= _atClient.getLocalSecondary()!; var updateSharedKeyBuilder = UpdateVerbBuilder() ..atKey = AtConstants.atEncryptionSharedKey ..sharedWith = atKey.sharedWith