Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Replace EncryptionUtil decryption methods with at_chops #1185

Merged
merged 10 commits into from
Jan 3, 2024
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -10,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
Expand All @@ -32,7 +34,25 @@ class LocalKeyDecryption extends AbstractAtKeyEncryption
intent: Intent.fetchEncryptionSharedKey,
exceptionScenario: ExceptionScenario.fetchEncryptionKeys);
}
return EncryptionUtil.decryptValue(encryptedValue, symmetricKey,
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(symmetricKey));
decryptionResultFromAtChops = _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;
}
}
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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<dynamic> decrypt(AtKey atKey, dynamic encryptedValue) async {
if (encryptedValue == null ||
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -31,64 +29,76 @@ 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 {
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 != 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.decryptionFailed);
}

AtEncryptionResult decryptionResultFromAtChops;
try {
final decryptionResult = atClient.atChops!
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<String> _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 {
Expand All @@ -102,7 +112,7 @@ class SharedKeyDecryption implements AtKeyDecryption {
..atKey = AtConstants.atEncryptionSharedKey
..sharedBy = atKey.sharedBy
..auth = true;
encryptedSharedKey = await atClient
encryptedSharedKey = await _atClient
.getRemoteSecondary()!
.executeVerb(sharedKeyLookUpBuilder);
encryptedSharedKey =
Expand Down
2 changes: 2 additions & 0 deletions packages/at_client/lib/src/util/encryption_util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it safe to mark this class and all of the methods as @Deprecated now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 4 sub tasks in #1145
this PR is for first sub task. I am currently working on second sub task.
Once all sub tasks are done, we can mark the methods deprecated

class EncryptionUtil {
static final _logger = AtSignLogger('EncryptionUtil');

Expand Down
36 changes: 4 additions & 32 deletions packages/at_client/test/decryption_service_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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'));
Expand All @@ -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<LLookupVerbBuilder>()))
.thenAnswer((_) async => 'dummy_shared_key');
when(() => mockAtClientImpl.atChops).thenAnswer((_) => mockAtChops);
});
Expand Down Expand Up @@ -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',
() {
Expand Down
Loading