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 45a51e9fc..0e372ea94 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 @@ -75,12 +75,6 @@ class SharedKeyDecryption implements AtKeyDecryption { 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()}'); diff --git a/packages/at_client/lib/src/encryption_service/self_key_encryption.dart b/packages/at_client/lib/src/encryption_service/self_key_encryption.dart index 6545f3c26..87f9a4e19 100644 --- a/packages/at_client/lib/src/encryption_service/self_key_encryption.dart +++ b/packages/at_client/lib/src/encryption_service/self_key_encryption.dart @@ -5,17 +5,18 @@ import 'package:at_client/src/response/default_response_parser.dart'; import 'package:at_client/src/util/at_client_util.dart'; import 'package:at_client/src/util/encryption_util.dart'; import 'package:at_commons/at_commons.dart'; +import 'package:at_chops/at_chops.dart'; import 'package:at_utils/at_logger.dart'; ///Class responsible for encrypting the selfKey's class SelfKeyEncryption implements AtKeyEncryption { late final AtSignLogger _logger; - final AtClient atClient; + final AtClient _atClient; - SelfKeyEncryption(this.atClient) { + SelfKeyEncryption(this._atClient) { _logger = - AtSignLogger('SelfKeyEncryption (${atClient.getCurrentAtSign()})'); + AtSignLogger('SelfKeyEncryption (${_atClient.getCurrentAtSign()})'); } @override @@ -29,12 +30,27 @@ class SelfKeyEncryption implements AtKeyEncryption { } // Get AES key for current atSign var selfEncryptionKey = - await _getSelfEncryptionKey(atClient.getLocalSecondary()!); + await _getSelfEncryptionKey(_atClient.getLocalSecondary()!); selfEncryptionKey = DefaultResponseParser().parse(selfEncryptionKey).response; - // Encrypt value using sharedKey - return EncryptionUtil.encryptValue(value, selfEncryptionKey, - ivBase64: atKey.metadata.ivNonce); + AtEncryptionResult encryptionResultFromAtChops; + try { + InitialisationVector iV; + if (atKey.metadata.ivNonce != null) { + iV = AtChopsUtil.generateIVFromBase64String(atKey.metadata.ivNonce!); + } else { + iV = AtChopsUtil.generateIVLegacy(); + } + var encryptionAlgo = AESEncryptionAlgo(AESKey(selfEncryptionKey)); + encryptionResultFromAtChops = _atClient.atChops!.encryptString( + value, EncryptionKeyType.aes256, + encryptionAlgorithm: encryptionAlgo, iv: iV); + } on AtEncryptionException catch (e) { + _logger.severe( + 'encryption exception during self encryption of key: ${atKey.key}. Reason: ${e.toString()}'); + rethrow; + } + return encryptionResultFromAtChops.result; } Future _getSelfEncryptionKey(LocalSecondary localSecondary) async { diff --git a/packages/at_client/lib/src/encryption_service/shared_key_encryption.dart b/packages/at_client/lib/src/encryption_service/shared_key_encryption.dart index 7fc1cfaab..4cbb90d2a 100644 --- a/packages/at_client/lib/src/encryption_service/shared_key_encryption.dart +++ b/packages/at_client/lib/src/encryption_service/shared_key_encryption.dart @@ -1,9 +1,16 @@ import 'package:at_client/at_client.dart'; +import 'package:at_utils/at_logger.dart'; +import 'package:at_chops/at_chops.dart'; import 'package:at_client/src/encryption_service/abstract_atkey_encryption.dart'; ///Class responsible for encrypting the value of the SharedKey's class SharedKeyEncryption extends AbstractAtKeyEncryption { - SharedKeyEncryption(AtClient atClient) : super(atClient); + final AtClient _atClient; + late final AtSignLogger _logger; + SharedKeyEncryption(this._atClient) : super(_atClient) { + _logger = + AtSignLogger('SelfKeyEncryption (${_atClient.getCurrentAtSign()})'); + } @override Future encrypt(AtKey atKey, dynamic value, @@ -17,9 +24,23 @@ class SharedKeyEncryption extends AbstractAtKeyEncryption { // encryption key and setting it in super.sharedKey await super.encrypt(atKey, value, storeSharedKeyEncryptedWithData: storeSharedKeyEncryptedWithData); - - // Encrypt the value - return EncryptionUtil.encryptValue(value, sharedKey, - ivBase64: atKey.metadata.ivNonce); + AtEncryptionResult encryptionResultFromAtChops; + try { + InitialisationVector iV; + if (atKey.metadata.ivNonce != null) { + iV = AtChopsUtil.generateIVFromBase64String(atKey.metadata.ivNonce!); + } else { + iV = AtChopsUtil.generateIVLegacy(); + } + var encryptionAlgo = AESEncryptionAlgo(AESKey(sharedKey)); + encryptionResultFromAtChops = _atClient.atChops!.encryptString( + value, EncryptionKeyType.aes256, + encryptionAlgorithm: encryptionAlgo, iv: iV); + } on AtEncryptionException catch (e) { + _logger.severe( + 'encryption exception during shared key encryption of key: ${atKey.key}. Reason: ${e.toString()}'); + rethrow; + } + return encryptionResultFromAtChops.result; } } diff --git a/packages/at_client/test/encryption_service_test.dart b/packages/at_client/test/encryption_service_test.dart index a98787e0f..bfa682f0b 100644 --- a/packages/at_client/test/encryption_service_test.dart +++ b/packages/at_client/test/encryption_service_test.dart @@ -49,6 +49,8 @@ void main() { when(() => mockAtClient.atChops).thenAnswer((_) => mockAtChops); when(() => mockAtClient.getLocalSecondary()) .thenAnswer((_) => mockLocalSecondary); + when(() => mockAtClient.getRemoteSecondary()) + .thenAnswer((_) => mockRemoteSecondary); mockSigningResult = AtSigningResult()..result = 'mock_signing_result'; registerFallbackValue(FakeAtSigningInput()); when(() => mockAtChops.sign(any())).thenAnswer((_) => mockSigningResult); @@ -76,42 +78,6 @@ void main() { }); }); - group('A group of tests related positive scenario of encryption', () { - test( - 'A test to verify value gets legacy encrypted when self encryption key is available', - () async { - var selfEncryptionKey = 'REqkIcl9HPekt0T7+rZhkrBvpysaPOeC2QL1PVuWlus='; - var value = 'self_key_value'; - when(() => mockLocalSecondary.getEncryptionSelfKey()) - .thenAnswer((_) => Future.value(selfEncryptionKey)); - var selfKeyEncryption = SelfKeyEncryption(mockAtClient); - var encryptedData = await selfKeyEncryption.encrypt( - AtKey.self('phone', namespace: 'wavi').build(), value); - var response = - EncryptionUtil.decryptValue(encryptedData, selfEncryptionKey); - expect(response, value); - }); - - test( - 'A test to verify value gets encrypted when self encryption key is available', - () async { - var selfEncryptionKey = 'REqkIcl9HPekt0T7+rZhkrBvpysaPOeC2QL1PVuWlus='; - var value = 'self_key_value'; - when(() => mockLocalSecondary.getEncryptionSelfKey()) - .thenAnswer((_) => Future.value(selfEncryptionKey)); - var selfKeyEncryption = SelfKeyEncryption(mockAtClient); - - var atKey = AtKey.self('phone', namespace: 'wavi').build(); - atKey.metadata.ivNonce = EncryptionUtil.generateIV(); - - var encryptedData = await selfKeyEncryption.encrypt(atKey, value); - var response = EncryptionUtil.decryptValue( - encryptedData, selfEncryptionKey, - ivBase64: atKey.metadata.ivNonce); - expect(response, value); - }); - }); - group('A group of test to sign the public data', () { test('A test to verify the sign the public data', () async { String encryptionPrivateKey = @@ -370,10 +336,11 @@ void main() { when(() => mockLocalSecondary .executeVerb(any(that: EncryptionPublicKeyMatcher()))) .thenAnswer((_) => Future.value(encryptionPublicKey)); - when(() => mockAtChops.decryptString( - encryptedSharedKey, EncryptionKeyType.rsa2048)) - .thenAnswer((_) => (AtEncryptionResult()..result = sharedKey)); - + var encryptionKeyPair = + AtEncryptionKeyPair.create(encryptionPublicKey, encryptionPrivateKey); + AtChopsKeys atChopsKeys = AtChopsKeys.create(encryptionKeyPair, null); + var atChopsImpl = AtChopsImpl(atChopsKeys); + when(() => mockAtClient.atChops).thenAnswer((_) => atChopsImpl); var encryptedValue = await sharedKeyEncryption.encrypt(atKey, value); expect(atKey.metadata.sharedKeyEnc.isNotNull, true); expect(atKey.metadata.pubKeyCS.isNotNull, true); @@ -407,10 +374,11 @@ void main() { when(() => mockLocalSecondary .executeVerb(any(that: EncryptionPublicKeyMatcher()))) .thenAnswer((_) => Future.value(encryptionPublicKey)); - when(() => mockAtChops.decryptString( - encryptedSharedKey, EncryptionKeyType.rsa2048)) - .thenAnswer((_) => (AtEncryptionResult()..result = sharedKey)); - + var encryptionKeyPair = + AtEncryptionKeyPair.create(encryptionPublicKey, encryptionPrivateKey); + AtChopsKeys atChopsKeys = AtChopsKeys.create(encryptionKeyPair, null); + var atChopsImpl = AtChopsImpl(atChopsKeys); + when(() => mockAtClient.atChops).thenAnswer((_) => atChopsImpl); var encryptedValue = await sharedKeyEncryption.encrypt(atKey, value); var decryptedSharedKey = // ignore: deprecated_member_use_from_same_package @@ -453,6 +421,11 @@ void main() { sync: false)).thenAnswer((_) => Future.value('data:1')); when(() => mockLocalSecondary.getEncryptionPublicKey('@alice')) .thenAnswer((_) => Future.value(encryptionPublicKey)); + var encryptionKeyPair = + AtEncryptionKeyPair.create(encryptionPublicKey, encryptionPrivateKey); + AtChopsKeys atChopsKeys = AtChopsKeys.create(encryptionKeyPair, null); + var atChopsImpl = AtChopsImpl(atChopsKeys); + when(() => mockAtClient.atChops).thenAnswer((_) => atChopsImpl); var atKey = (AtKey.shared('phone', namespace: 'wavi', sharedBy: '@alice') ..sharedWith('@bob')) @@ -490,9 +463,17 @@ void main() { .thenAnswer((_) => Future.value(encryptionPublicKey)); when(() => mockLocalSecondary.executeVerb( any(that: UpdateEncryptedSharedKeyMatcher()), - sync: true)).thenAnswer((_) => Future.value('data:1')); + sync: false)).thenAnswer((_) => Future.value('data:1')); + when(() => mockRemoteSecondary.executeVerb( + any(that: UpdateEncryptedSharedKeyMatcher()), + sync: false)).thenAnswer((_) => Future.value('data:1')); when(() => mockLocalSecondary.getEncryptionPublicKey('@alice')) .thenAnswer((_) => Future.value(encryptionPublicKey)); + var encryptionKeyPair = + AtEncryptionKeyPair.create(encryptionPublicKey, encryptionPrivateKey); + AtChopsKeys atChopsKeys = AtChopsKeys.create(encryptionKeyPair, null); + var atChopsImpl = AtChopsImpl(atChopsKeys); + when(() => mockAtClient.atChops).thenAnswer((_) => atChopsImpl); var atKey = (AtKey.shared('phone', namespace: 'wavi', sharedBy: '@alice') ..sharedWith('@bob')) @@ -597,7 +578,9 @@ class UpdateEncryptedSharedKeyMatcher extends Matcher { @override bool matches(item, Map matchState) { + print('inside matches'); if (item is UpdateVerbBuilder && item.atKey.key.contains('shared_key')) { + print('match'); return true; } return false; diff --git a/packages/at_client/test/self_key_encryption_test.dart b/packages/at_client/test/self_key_encryption_test.dart new file mode 100644 index 000000000..671c47a10 --- /dev/null +++ b/packages/at_client/test/self_key_encryption_test.dart @@ -0,0 +1,78 @@ +import 'package:at_chops/at_chops.dart'; +import 'package:at_client/src/decryption_service/self_key_decryption.dart'; +import 'package:at_client/src/encryption_service/self_key_encryption.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 {} + +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()); + }); + + test('test to check encryption/decryption of self keys', () async { + // This test encrypts a self key value and then checks whether decrypted value is same as original value + // If @alice wants to maintain a location without sharing to anyone then the key-value format will be @alice:location@alice New Jersey + // @alice uses self encryption AES key generated during onboarding process to encrypt the value. Same key is used for decryption + var selfKeyEncryption = SelfKeyEncryption(mockAtClient); + var selfKeyDecryption = SelfKeyDecryption(mockAtClient); + // generate new AES key for the test + var aliceSelfEncryptionKey = + AtChopsUtil.generateSymmetricKey(EncryptionKeyType.aes256).key; + // set atChops + AtChopsKeys atChopsKeys = AtChopsKeys.create(null, null); + atChopsKeys.selfEncryptionKey = AESKey(aliceSelfEncryptionKey); + var atChopsImpl = AtChopsImpl(atChopsKeys); + when(() => mockAtClient.atChops).thenAnswer((_) => atChopsImpl); + when(() => mockLocalSecondary.getEncryptionSelfKey()) + .thenAnswer((_) => Future.value(aliceSelfEncryptionKey)); + var selfKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@bob' + ..key = 'location'; + var location = 'New Jersey'; + var encryptedValue = await selfKeyEncryption.encrypt(selfKey, location); + expect(encryptedValue != location, true); + var decryptionResult = + await selfKeyDecryption.decrypt(selfKey, encryptedValue); + expect(decryptionResult, location); + }); + test( + 'test to check self key encryption throws exception when passed value is not string type', + () async { + var selfKeyEncryption = SelfKeyEncryption(mockAtClient); + var selfKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@bob' + ..key = 'location'; + var locations = ['new jersey', 'new york']; + expect( + () async => await selfKeyEncryption.encrypt(selfKey, locations), + throwsA(predicate((dynamic e) => + e is AtEncryptionException && + e.message == + 'Invalid value type found: List. Valid value type is String'))); + }); +} diff --git a/packages/at_client/test/shared_key_encryption_test.dart b/packages/at_client/test/shared_key_encryption_test.dart new file mode 100644 index 000000000..8559c396b --- /dev/null +++ b/packages/at_client/test/shared_key_encryption_test.dart @@ -0,0 +1,220 @@ +import 'package:at_chops/at_chops.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: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 verify encryption and decryption of a key shared by @alice with @bob - without IV', + () async { + // This test verifies encryption and decryption of a shared key value without using Initialization vector(IV) + // Value of a shared key is encrypted and then test asserts whether decrypted value is same as original value + // If @alice wants to share location value with bob, then key-value format is @bob:location@alice California + // @alice will generate a AES key and will encrypt the location value - California + // The AES key will be encrypted with @bob's public key and stored in @bob:shared_key@alice + // When @bob wants to decrypt the @alice's location, @bob will read the encrypted AES key from @bob:shared_key@alice + // @bob will decrypt the AES key using @bob's private key + // @bob will decrypt the location value - California with AES key + + var sharedKeyEncryption = SharedKeyEncryption(mockAtClient); + var bobMockAtClient = MockAtClientImpl(); + var bobMockLocalSecondary = MockLocalSecondary(); + // set up encryption key pair for @alice. This will be used during encryption process + var aliceEncryptionKeyPair = AtChopsUtil.generateAtEncryptionKeyPair(); + var aliceEncryptionPublicKey = aliceEncryptionKeyPair.atPublicKey.publicKey; + // Set up encryption key pair for @bob. This will be used during decryption process + var bobEncryptionKeyPair = AtChopsUtil.generateAtEncryptionKeyPair(); + var bobEncryptionPublicKey = bobEncryptionKeyPair.atPublicKey.publicKey; + + // Generate the AES for encrypting the location value + var aesSharedKey = + AtChopsUtil.generateSymmetricKey(EncryptionKeyType.aes256).key; + // set atChops for bob + AtChopsKeys bobAtChopsKeys = AtChopsKeys.create(bobEncryptionKeyPair, null); + var bobAtChopsImpl = AtChopsImpl(bobAtChopsKeys); + when(() => bobMockAtClient.atChops).thenAnswer((_) => bobAtChopsImpl); + when(() => bobMockAtClient.getLocalSecondary()) + .thenAnswer((_) => bobMockLocalSecondary); + when(() => bobMockAtClient.getCurrentAtSign()).thenReturn('@bob'); + when(() => bobMockLocalSecondary.getEncryptionPublicKey('@bob')) + .thenAnswer((_) => Future.value(bobEncryptionPublicKey)); + var sharedKeyDecryption = SharedKeyDecryption(bobMockAtClient); + + // encrypted AES key @bob:shared_key@alice + var sharedKeyEncryptedWithBobPublicKey = + EncryptionUtil.encryptKey(aesSharedKey, bobEncryptionPublicKey); + + // local copy of the AES key that @alice maintains - shared_key.bob@alice + var sharedKeyEncryptedWithAlicePublicKey = + EncryptionUtil.encryptKey(aesSharedKey, aliceEncryptionPublicKey); + + var bobPublicKeyCheckSum = + EncryptionUtil.md5CheckSum(bobEncryptionPublicKey); + var location = 'California'; + + // set atChops for alice + AtChopsKeys atChopsKeys = AtChopsKeys.create(aliceEncryptionKeyPair, 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((Invocation invocation) { + final builder = invocation.positionalArguments[0] as LLookupVerbBuilder; + final buildKeyValue = builder.buildKey(); + if (buildKeyValue == 'shared_key.bob@alice') { + return Future.value('data:$sharedKeyEncryptedWithAlicePublicKey'); + } else if (buildKeyValue == 'cached:public:publickey@bob') { + return Future.value('data:$bobEncryptionPublicKey'); + } else if (buildKeyValue == '@bob:shared_key@alice') { + return Future.value('data:$sharedKeyEncryptedWithBobPublicKey'); + } else { + return Future.value('data:null'); + } + }); + var sharedKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@bob' + ..key = 'location'; + sharedKey.metadata = (Metadata()..pubKeyCS = bobPublicKeyCheckSum); + var encryptionResult = + await sharedKeyEncryption.encrypt(sharedKey, location); + expect(encryptionResult != location, true); + var decryptionResult = + await sharedKeyDecryption.decrypt(sharedKey, encryptionResult); + expect(decryptionResult, location); + }); + + test( + 'test to verify encryption and decryption of a key shared by @alice with @bob - with IV', + () async { + // This test verifies encryption and decryption of a shared key value by using Initialization vector(IV) + // If @alice wants to share location value with bob, then key-value format is @bob:location@alice California + // @alice will generate a AES key and will encrypt the location value - California + // The AES key will be encrypted with @bob's public key and stored in @bob:shared_key@alice + // When @bob wants to decrypt the @alice's location, @bob will read the encrypted AES key from @bob:shared_key@alice + // @bob will decrypt the AES key using @bob's private key + // @bob will decrypt the location value - California with AES key + + var sharedKeyEncryption = SharedKeyEncryption(mockAtClient); + var bobMockAtClient = MockAtClientImpl(); + var bobMockLocalSecondary = MockLocalSecondary(); + // set up encryption key pair for @alice. This will be used during encryption process + var aliceEncryptionKeyPair = AtChopsUtil.generateAtEncryptionKeyPair(); + var aliceEncryptionPublicKey = aliceEncryptionKeyPair.atPublicKey.publicKey; + // Set up encryption key pair for @bob. This will be used during decryption process + var bobEncryptionKeyPair = AtChopsUtil.generateAtEncryptionKeyPair(); + var bobEncryptionPublicKey = bobEncryptionKeyPair.atPublicKey.publicKey; + + // Generate the AES for encrypting the location value + var aesSharedKey = + AtChopsUtil.generateSymmetricKey(EncryptionKeyType.aes256).key; + // set atChops for bob + AtChopsKeys bobAtChopsKeys = AtChopsKeys.create(bobEncryptionKeyPair, null); + var bobAtChopsImpl = AtChopsImpl(bobAtChopsKeys); + when(() => bobMockAtClient.atChops).thenAnswer((_) => bobAtChopsImpl); + when(() => bobMockAtClient.getLocalSecondary()) + .thenAnswer((_) => bobMockLocalSecondary); + when(() => bobMockAtClient.getCurrentAtSign()).thenReturn('@bob'); + when(() => bobMockLocalSecondary.getEncryptionPublicKey('@bob')) + .thenAnswer((_) => Future.value(bobEncryptionPublicKey)); + var sharedKeyDecryption = SharedKeyDecryption(bobMockAtClient); + + // encrypted AES key @bob:shared_key@alice + var sharedKeyEncryptedWithBobPublicKey = + EncryptionUtil.encryptKey(aesSharedKey, bobEncryptionPublicKey); + + // local copy of the AES key that @alice maintains - shared_key.bob@alice + var sharedKeyEncryptedWithAlicePublicKey = + EncryptionUtil.encryptKey(aesSharedKey, aliceEncryptionPublicKey); + + var bobPublicKeyCheckSum = + EncryptionUtil.md5CheckSum(bobEncryptionPublicKey); + var location = 'California'; + + // set atChops for alice + AtChopsKeys atChopsKeys = AtChopsKeys.create(aliceEncryptionKeyPair, 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((Invocation invocation) { + final builder = invocation.positionalArguments[0] as LLookupVerbBuilder; + final buildKeyValue = builder.buildKey(); + if (buildKeyValue == 'shared_key.bob@alice') { + return Future.value('data:$sharedKeyEncryptedWithAlicePublicKey'); + } else if (buildKeyValue == 'cached:public:publickey@bob') { + return Future.value('data:$bobEncryptionPublicKey'); + } else if (buildKeyValue == '@bob:shared_key@alice') { + return Future.value('data:$sharedKeyEncryptedWithBobPublicKey'); + } else { + return Future.value('data:null'); + } + }); + var sharedKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@bob' + ..key = 'location'; + // random IV string + var ivBase64String = 'YmFzZTY0IGVuY29kaW5n'; + sharedKey.metadata = Metadata() + ..pubKeyCS = bobPublicKeyCheckSum + ..ivNonce = ivBase64String; + var encryptionResult = + await sharedKeyEncryption.encrypt(sharedKey, location); + expect(encryptionResult != location, true); + var decryptionResult = + await sharedKeyDecryption.decrypt(sharedKey, encryptionResult); + expect(decryptionResult, location); + }); + test( + 'test to check shared key encryption throws exception when passed value is not string type', + () async { + var sharedKeyEncryption = SharedKeyEncryption(mockAtClient); + var selfKey = AtKey() + ..sharedBy = '@alice' + ..sharedWith = '@bob' + ..key = 'location'; + var locations = ['new jersey', 'new york']; + expect( + () async => await sharedKeyEncryption.encrypt(selfKey, locations), + throwsA(predicate((dynamic e) => + e is AtEncryptionException && + e.message == + 'Invalid value type found: List. Valid value type is String'))); + }); +}