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/abstract_atkey_encryption.dart b/packages/at_client/lib/src/encryption_service/abstract_atkey_encryption.dart index 3e4342eec..78612f25a 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 @@ -40,7 +40,6 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { Future encrypt(AtKey atKey, dynamic value, {bool storeSharedKeyEncryptedWithData = true}) async { _sharedKey = await getMyCopyOfSharedSymmetricKey(atKey); - if (_sharedKey.isEmpty) { _sharedKey = await createMyCopyOfSharedSymmetricKey(atKey); } @@ -121,26 +120,15 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption { Future createMyCopyOfSharedSymmetricKey(AtKey atKey) async { _logger.info( "Creating new shared symmetric key as ${atKey.sharedBy} for ${atKey.sharedWith}"); - // Fetch our encryption public key - String? currentAtSignEncryptionPublicKey; - try { - currentAtSignEncryptionPublicKey = await _atClient - .getLocalSecondary()! - .getEncryptionPublicKey(atKey.sharedBy!); - } on KeyNotFoundException catch (e) { - e.stack(AtChainedException( - Intent.fetchEncryptionPublicKey, - ExceptionScenario.fetchEncryptionKeys, - 'Failed to fetch encryption public key of current atSign')); - rethrow; - } // Generate new symmetric key - var newSymmetricKeyBase64 = EncryptionUtil.generateAESKey(); - + var newSymmetricKeyBase64 = + AtChopsUtil.generateSymmetricKey(EncryptionKeyType.aes256).key; // Encrypt the new symmetric key with our public key - var encryptedSharedKeyMyCopy = EncryptionUtil.encryptKey( - newSymmetricKeyBase64, currentAtSignEncryptionPublicKey!); - + var atChopsEncryptionResult = _atClient.atChops! + .encryptString(newSymmetricKeyBase64, EncryptionKeyType.rsa2048); + var encryptedSharedKeyMyCopy = atChopsEncryptionResult.result; + _logger.info( + 'encryptedSharedKeyMyCopy from atChops: $encryptedSharedKeyMyCopy'); // Defensive code to ensure that we do not have an old 'their' copy on atServer await deleteTheirCopyOfEncryptedSharedKey( atKey, _atClient.getRemoteSecondary()!); 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..7adf0404d 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 @@ -3,19 +3,19 @@ import 'package:at_client/src/client/local_secondary.dart'; import 'package:at_client/src/encryption_service/encryption.dart'; 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 +29,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/at_chops_encryption_util_compatibility_test.dart b/packages/at_client/test/at_chops_encryption_util_compatibility_test.dart new file mode 100644 index 000000000..0b7fb7812 --- /dev/null +++ b/packages/at_client/test/at_chops_encryption_util_compatibility_test.dart @@ -0,0 +1,119 @@ +import 'package:at_chops/at_chops.dart'; +import 'package:at_client/at_client.dart'; +import 'package:test/test.dart'; + +// Tests to verify whether encryption with EncryptionUtil, decryption with AtChops works and vice versa. +void main() { + test( + 'A test to verify encrypting AES key with encryption util and decryption with at_chops', + () { + // Generate RSA key pair. Generate AES key. Encrypt AES key using RSA public key using EncryptionUtil method + // Decrypt encryptedAESKey using AtChops (uses RSA private key) + var encryptionKeyPair = AtChopsUtil.generateAtEncryptionKeyPair(); + var encryptionPublicKey = encryptionKeyPair.atPublicKey.publicKey; + var aesKey = EncryptionUtil.generateAESKey(); + var encryptedAesKey = + EncryptionUtil.encryptKey(aesKey, encryptionPublicKey); + AtChopsKeys atChopsKeys = AtChopsKeys.create(encryptionKeyPair, null); + var atChops = AtChopsImpl(atChopsKeys); + var decryptedAesKey = atChops + .decryptString(encryptedAesKey, EncryptionKeyType.rsa2048) + .result; + expect(decryptedAesKey, aesKey); + }); + test( + 'A test to verify encrypting AES key with at_chops and decryption with EncryptionUtil', + () { + // Generate RSA key pair. Generate AES key. Encrypt AES key using AtChops(uses RSA public key) + // Decrypt encryptedAESKey with EncryptionUtil using RSA private key + var encryptionKeyPair = AtChopsUtil.generateAtEncryptionKeyPair(); + var encryptionPrivateKey = encryptionKeyPair.atPrivateKey.privateKey; + var aesKey = AtChopsUtil.generateSymmetricKey(EncryptionKeyType.aes256).key; + + AtChopsKeys atChopsKeys = AtChopsKeys.create(encryptionKeyPair, null); + var atChops = AtChopsImpl(atChopsKeys); + var encryptedAesKey = + atChops.encryptString(aesKey, EncryptionKeyType.rsa2048).result; + + var decryptedAesKey = + //ignore: deprecated_member_use_from_same_package + EncryptionUtil.decryptKey(encryptedAesKey, encryptionPrivateKey); + expect(decryptedAesKey, aesKey); + }); + + test( + 'A test to verify data encryption with encryption util and decryption with at_chops', + () { + // Generate AES key. Encrypt data with EncryptionUtil using AES key + // Create a AESEncryption algo object using AES key and pass it to AtChops. Decrypt the encrypted value with AtChops + var aesKey = EncryptionUtil.generateAESKey(); + var dataToEncrypt = 'alice@atsign.com'; + var encryptedData = EncryptionUtil.encryptValue(dataToEncrypt, aesKey); + var encryptionAlgo = AESEncryptionAlgo(AESKey(aesKey)); + AtChopsKeys atChopsKeys = AtChopsKeys.create(null, null); + var atChops = AtChopsImpl(atChopsKeys); + var decryptedData = atChops + .decryptString(encryptedData, EncryptionKeyType.aes256, + encryptionAlgorithm: encryptionAlgo, + iv: AtChopsUtil.generateIVLegacy()) + .result; + expect(decryptedData, dataToEncrypt); + }); + + test( + 'A test to verify data encryption with at_chops and decryption with encryption_util', + () { + // Generate AES key. Encrypt data with AtChops using AES key + // Decrypt the encrypted value with EncryptionUtil + var aesKey = AtChopsUtil.generateSymmetricKey(EncryptionKeyType.aes256); + var dataToEncrypt = 'alice@atsign.com'; + var encryptionAlgo = AESEncryptionAlgo(AESKey(aesKey.key)); + AtChopsKeys atChopsKeys = AtChopsKeys.create(null, null); + var atChops = AtChopsImpl(atChopsKeys); + var encryptedData = atChops + .encryptString(dataToEncrypt, EncryptionKeyType.aes256, + encryptionAlgorithm: encryptionAlgo, + iv: AtChopsUtil.generateIVLegacy()) + .result; + var decryptedData = EncryptionUtil.decryptValue(encryptedData, aesKey.key); + expect(decryptedData, dataToEncrypt); + }); + + test( + 'A test to verify data(with emoji) encryption with encryption util and decryption with at_chops', + () { + // Generate AES key. Encrypt data with EncryptionUtil using AES key + // Create a AESEncryption algo object using AES key and pass it to AtChops. Decrypt the encrypted value with AtChops + var aesKey = EncryptionUtil.generateAESKey(); + var dataToEncrypt = 'alice@🦄🛠'; + var encryptedData = EncryptionUtil.encryptValue(dataToEncrypt, aesKey); + var encryptionAlgo = AESEncryptionAlgo(AESKey(aesKey)); + AtChopsKeys atChopsKeys = AtChopsKeys.create(null, null); + var atChops = AtChopsImpl(atChopsKeys); + var decryptedData = atChops + .decryptString(encryptedData, EncryptionKeyType.aes256, + encryptionAlgorithm: encryptionAlgo, + iv: AtChopsUtil.generateIVLegacy()) + .result; + expect(decryptedData, dataToEncrypt); + }); + + test( + 'A test to verify data(with emoji) encryption with at_chops and decryption with encryption_util', + () { + // Generate AES key. Encrypt data with AtChops using AES key + // Decrypt the encrypted value with EncryptionUtil + var aesKey = AtChopsUtil.generateSymmetricKey(EncryptionKeyType.aes256); + var dataToEncrypt = 'alice@🦄🛠'; + var encryptionAlgo = AESEncryptionAlgo(AESKey(aesKey.key)); + AtChopsKeys atChopsKeys = AtChopsKeys.create(null, null); + var atChops = AtChopsImpl(atChopsKeys); + var encryptedData = atChops + .encryptString(dataToEncrypt, EncryptionKeyType.aes256, + encryptionAlgorithm: encryptionAlgo, + iv: AtChopsUtil.generateIVLegacy()) + .result; + var decryptedData = EncryptionUtil.decryptValue(encryptedData, aesKey.key); + expect(decryptedData, dataToEncrypt); + }); +} diff --git a/packages/at_client/test/encryption_service_test.dart b/packages/at_client/test/encryption_service_test.dart index a98787e0f..4ab33fc02 100644 --- a/packages/at_client/test/encryption_service_test.dart +++ b/packages/at_client/test/encryption_service_test.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:at_chops/at_chops.dart'; import 'package:at_client/at_client.dart'; import 'package:at_client/src/encryption_service/encryption_manager.dart'; import 'package:at_client/src/encryption_service/self_key_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:mocktail/mocktail.dart'; import 'package:test/test.dart'; -import 'package:at_chops/at_chops.dart'; class MockAtClientManager extends Mock implements AtClientManager {} @@ -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')) @@ -540,7 +521,8 @@ void main() { 'unable to connect to remote secondary')); when(() => mockLocalSecondary.getEncryptionPublicKey('@alice')) .thenAnswer((_) => Future.value(encryptionPublicKey)); - + when(() => mockAtChops.encryptString(any(), EncryptionKeyType.rsa2048)) + .thenAnswer((_) => AtEncryptionResult()..result = 'random'); var atKey = (AtKey.shared('phone', namespace: 'wavi', sharedBy: '@alice') ..sharedWith('@bob')) .build(); @@ -597,7 +579,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/shared_key_encryption_test.dart b/packages/at_client/test/shared_key_encryption_test.dart index 8559c396b..5f3e31328 100644 --- a/packages/at_client/test/shared_key_encryption_test.dart +++ b/packages/at_client/test/shared_key_encryption_test.dart @@ -60,6 +60,10 @@ void main() { // Generate the AES for encrypting the location value var aesSharedKey = AtChopsUtil.generateSymmetricKey(EncryptionKeyType.aes256).key; + // local copy of the AES key that @alice maintains - shared_key.bob@alice + var sharedKeyEncryptedWithAlicePublicKey = + EncryptionUtil.encryptKey(aesSharedKey, aliceEncryptionPublicKey); + // set atChops for bob AtChopsKeys bobAtChopsKeys = AtChopsKeys.create(bobEncryptionKeyPair, null); var bobAtChopsImpl = AtChopsImpl(bobAtChopsKeys); @@ -75,10 +79,6 @@ void main() { 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'; @@ -201,6 +201,98 @@ void main() { await sharedKeyDecryption.decrypt(sharedKey, encryptionResult); expect(decryptionResult, location); }); + test( + 'test to verify encryption and decryption of a key shared by @alice with @bob - without IV - local copy of shared AES key is null', + () async { + // This test verifies encryption and decryption of a shared key value without using Initialization vector(IV) + // Local copy shared_key.bob@alice is null. Remote copy shared_key.bob@alice exists in @alice secondary + // 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; + + // remote copy of the AES key from @alice secondary - shared_key.bob@alice + var sharedKeyEncryptedWithAlicePublicKey = + EncryptionUtil.encryptKey(aesSharedKey, aliceEncryptionPublicKey); + + // encrypted AES key @bob:shared_key@alice + var sharedKeyEncryptedWithBobPublicKey = + EncryptionUtil.encryptKey(aesSharedKey, bobEncryptionPublicKey); + + // 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); + + 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(that: LLookupLocalSharedKeyMatcher()))).thenAnswer((_) => + throw KeyNotFoundException('local key shared_key.bob@alice not found')); + when(() => mockLocalSecondary.executeVerb( + any(that: DeleteLocalSharedKeyMatcher()), + sync: false)).thenAnswer((_) => Future.value('data:1')); + when(() => mockLocalSecondary.executeVerb( + any(that: UpdateLocalSharedKeyMatcher()), + sync: false)).thenAnswer((_) => Future.value('data:2')); + when(() => mockRemoteSecondary.executeVerb( + any(that: LLookupLocalSharedKeyMatcher()), + sync: false)) + .thenAnswer( + (_) => Future.value('data:$sharedKeyEncryptedWithAlicePublicKey')); + when(() => mockLocalSecondary + .executeVerb(any(that: LLookupTheirSharedKeyMatcher()))) + .thenAnswer( + (_) => Future.value('data:$sharedKeyEncryptedWithBobPublicKey')); + when(() => mockLocalSecondary + .executeVerb(any(that: LLookupCachedBobPublicKeyMatcher()))) + .thenAnswer((_) => Future.value('data:$bobEncryptionPublicKey')); + 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 check shared key encryption throws exception when passed value is not string type', () async { @@ -218,3 +310,82 @@ void main() { 'Invalid value type found: List. Valid value type is String'))); }); } + +class LLookupLocalSharedKeyMatcher extends Matcher { + @override + Description describe(Description description) => description + .add('A custom matcher to match LOOKUPS of encrypted shared key'); + + @override + bool matches(item, Map matchState) { + if (item is LLookupVerbBuilder && item.atKey.key == 'shared_key.bob') { + return true; + } + return false; + } +} + +class LLookupCachedBobPublicKeyMatcher extends Matcher { + @override + Description describe(Description description) => description + .add('A custom matcher to match LOOKUPS of encrypted shared key'); + + @override + bool matches(item, Map matchState) { + if (item is LLookupVerbBuilder && + item.atKey.key == 'publickey' && + item.atKey.sharedBy == '@bob' && + item.atKey.metadata.isPublic == true) { + return true; + } + return false; + } +} + +class LLookupTheirSharedKeyMatcher extends Matcher { + @override + Description describe(Description description) => description + .add('A custom matcher to match LOOKUPS of encrypted shared key'); + + @override + bool matches(item, Map matchState) { + if (item is LLookupVerbBuilder && + item.atKey.key == 'shared_key' && + item.atKey.sharedWith == '@bob' && + item.atKey.sharedBy == '@alice') { + return true; + } + return false; + } +} + +class UpdateLocalSharedKeyMatcher extends Matcher { + @override + Description describe(Description description) => description + .add('A custom matcher to match UPDATE of encrypted shared key'); + + @override + bool matches(item, Map matchState) { + if (item is UpdateVerbBuilder && item.atKey.key == 'shared_key.bob') { + return true; + } + return false; + } +} + +class DeleteLocalSharedKeyMatcher extends Matcher { + @override + Description describe(Description description) => description + .add('A custom matcher to match Deletes of encrypted shared key'); + + @override + bool matches(item, Map matchState) { + if (item is DeleteVerbBuilder && + item.atKey.key == 'shared_key' && + item.atKey.sharedWith == '@bob' && + item.atKey.sharedBy == '@alice') { + return true; + } + return false; + } +}