From 1fcd19dc6ae43ab07d2d4bb1b7ae10de4d66f8ad Mon Sep 17 00:00:00 2001 From: murali-shris Date: Thu, 26 Oct 2023 13:27:11 +0530 Subject: [PATCH] feat: modified examples for apkam enrollment --- packages/at_onboarding_cli/example/README.md | 30 ++++ .../example/apkam_authenticate.dart | 23 ++- .../example/apkam_enroll.dart | 22 ++- .../example/enroll_app_listen.dart | 137 ++++++++++++++++++ .../at_onboarding_cli/example/onboard.dart | 6 +- .../onboard/at_onboarding_service_impl.dart | 5 +- .../lib/src/util/home_directory_util.dart | 18 ++- packages/at_onboarding_cli/pubspec.yaml | 8 + 8 files changed, 228 insertions(+), 21 deletions(-) create mode 100644 packages/at_onboarding_cli/example/README.md create mode 100644 packages/at_onboarding_cli/example/enroll_app_listen.dart diff --git a/packages/at_onboarding_cli/example/README.md b/packages/at_onboarding_cli/example/README.md new file mode 100644 index 00000000..aa96cbac --- /dev/null +++ b/packages/at_onboarding_cli/example/README.md @@ -0,0 +1,30 @@ +List of steps to run the examples for checking apkam enrollment + +1. Onboard an atsign which has privilege to approve/deny enrollments +dart example/onboard.dart +e.g. dart example/onboard.dart @aliceđź›  /home/alice/.atsign/@aliceđź› _wavikey.atKeys +2. Authenticate using the onboarded atsign + dart example/apkam_authenticate.dart +e.g. dart example/apkam_authenticate.dart @aliceđź›  /home/alice/.atsign/@aliceđź› _wavikey.atKeys +3. Run client to approve enrollments + dart example/enroll_app_listen.dart + e.g dart example/enroll_app_listen.dart @aliceđź›  /home/alice/.atsign/@aliceđź› _wavikey.atKeys +4. Get OTP for enrollment + - 4.1 Pkam through ssl client + pkam:enrollmentId:: + enrollmentId - get from the .atKeys file + pkamChallenge - generate using the below commnd + at_tools/packages/at_pkam> + dart bin/main.dart -p + e.g dart bin/main.dart -p /home/alice/.atsign/@aliceđź› _wavikey.atKeys -r _70138292-07b5-4e47-8c94-e02e38220775@aliceđź› :883ea0aa-c526-400a-926e-48cae9281de9 + - 4.2 Once authenticated run otp:get +5. Request enrollment + - 5.1 Submit enrollment from new client + dart example/apkam_enroll.dart + e.g. dart example/apkam_enroll.dart @aliceđź›  /home/murali/.atsign/@aliceđź› _buzzkey.atKeys DY4UT4 + - 5.2 Approve the enrollment from the client from #3 + - 5.3 Enrollment should be successful and keys file stored in the path specified +6. Authenticate using the enrolled keys file + - 6.1 dart example/onboard.dart + + diff --git a/packages/at_onboarding_cli/example/apkam_authenticate.dart b/packages/at_onboarding_cli/example/apkam_authenticate.dart index 2e2f1cf9..fd8a29ac 100644 --- a/packages/at_onboarding_cli/example/apkam_authenticate.dart +++ b/packages/at_onboarding_cli/example/apkam_authenticate.dart @@ -1,17 +1,21 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:at_commons/at_commons.dart'; import 'package:at_onboarding_cli/at_onboarding_cli.dart'; import 'package:at_utils/at_logger.dart'; -Future main() async { - // final enrollIdFromServer = '867307c7-53bd-4736-8fe7-1520de58ce78'; - AtSignLogger.root_level = 'finest'; - final atSign = '@aliceđź› '; +Future main(List args) async { + AtSignLogger.root_level = 'info'; + final atSign = args[0]; AtOnboardingPreference atOnboardingPreference = AtOnboardingPreference() ..namespace = 'wavi' // unique identifier that can be used to identify data from your app - ..atKeysFilePath = '/home/murali/.atsign/@aliceđź› _key.atKeys' + ..atKeysFilePath = args[1] ..rootDomain = 'vip.ve.atsign.zone'; AtOnboardingService? onboardingService = AtOnboardingServiceImpl( - atSign, atOnboardingPreference); + atSign, atOnboardingPreference, + enrollmentId: _getEnrollmentIdFromKeysFile(args[1])); await onboardingService.authenticate(); // when authenticating // AtLookUp? atLookup = onboardingService.atLookUp; // AtClient? client = onboardingService.atClient; @@ -19,3 +23,10 @@ Future main() async { // print(await atLookup?.scan(regex: 'publickey')); // await onboardingService.close(); } + +String _getEnrollmentIdFromKeysFile(String keysFilePath) { + String atAuthData = File(keysFilePath).readAsStringSync(); + final enrollmentId = jsonDecode(atAuthData)[AtConstants.enrollmentId]; + print('**** enrollmentId: $enrollmentId'); + return enrollmentId; +} diff --git a/packages/at_onboarding_cli/example/apkam_enroll.dart b/packages/at_onboarding_cli/example/apkam_enroll.dart index df10d2f3..47cd29ce 100644 --- a/packages/at_onboarding_cli/example/apkam_enroll.dart +++ b/packages/at_onboarding_cli/example/apkam_enroll.dart @@ -1,21 +1,33 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:at_client/at_client.dart'; import 'package:at_onboarding_cli/at_onboarding_cli.dart'; import 'package:at_utils/at_logger.dart'; -Future main() async { +Future main(List args) async { AtSignLogger.root_level = 'finer'; - final atSign = '@alice'; + final atSign = args[0]; AtOnboardingPreference atOnboardingPreference = AtOnboardingPreference() ..namespace = 'buzz' // unique identifier that can be used to identify data from your app - ..atKeysFilePath = '/home/user/atsign/alice_buzzkey.atKeys' + ..atKeysFilePath = args[1] ..appName = 'buzz' ..deviceName = 'iphone' - ..rootDomain = 'vip.ve.atsign.zone'; + ..rootDomain = 'vip.ve.atsign.zone' + ..apkamAuthRetryDurationMins = 3; AtOnboardingService? onboardingService = AtOnboardingServiceImpl(atSign, atOnboardingPreference); Map namespaces = {"buzz": "rw"}; // run totp:get from enrolled client and pass the otp var enrollmentResponse = - await onboardingService.enroll('buzz', 'iphone', "068881", namespaces); + await onboardingService.enroll('buzz', 'iphone', args[2], namespaces); print('enrollmentResponse: $enrollmentResponse'); } + +String _getEnrollmentIdFromKeysFile(String keysFilePath) { + String atAuthData = File(keysFilePath).readAsStringSync(); + final enrollmentId = jsonDecode(atAuthData)[AtConstants.enrollmentId]; + print('**** enrollmentId: $enrollmentId'); + return enrollmentId; +} diff --git a/packages/at_onboarding_cli/example/enroll_app_listen.dart b/packages/at_onboarding_cli/example/enroll_app_listen.dart new file mode 100644 index 00000000..443222b7 --- /dev/null +++ b/packages/at_onboarding_cli/example/enroll_app_listen.dart @@ -0,0 +1,137 @@ +import 'dart:convert'; + +import 'package:at_auth/at_auth.dart'; +import 'package:at_chops/at_chops.dart'; +import 'package:at_client/at_client.dart'; +import 'dart:io'; +import 'package:at_auth/src/auth_constants.dart' as auth_constants; + +import 'atsign_preference.dart'; + +/// dart enroll_app_listen.dart +void main(List arguments) async { + var aliceAtSign = arguments[0]; + try { + var atAuthKeys = _decryptAtKeysFile(await _readAtKeysFile(arguments[1])); + var atChops = _createAtChops(atAuthKeys); + final atClientManager = await AtClientManager.getInstance() + .setCurrentAtSign( + aliceAtSign, + 'wavi', + AtSignPreference.getAlicePreference( + aliceAtSign, atAuthKeys.enrollmentId!), + atChops: atChops, + enrollmentId: atAuthKeys.enrollmentId); + + // alice - listen for notification + atClientManager.atClient.notificationService + .subscribe(regex: '.__manage') + .listen((notification) { + _notificationCallback(notification, atClientManager.atClient, atAuthKeys); + }); + } on Exception catch (e, trace) { + print(e.toString()); + print(trace); + } + + print('end of test'); +} + +Future _notificationCallback(AtNotification notification, + AtClient atClient, AtAuthKeys atAuthKeys) async { + print('alice enroll notification received: ${notification.toString()}'); + final notificationKey = notification.key; + final enrollmentId = + notificationKey.substring(0, notificationKey.indexOf('.new.enrollments')); + print('Approve enrollmentId $enrollmentId?'); + String? approveResponse = stdin.readLineSync(); + print('approved?: $approveResponse'); + var enrollRequest; + var enrollParamsJson = {}; + enrollParamsJson['enrollmentId'] = enrollmentId; + if (approveResponse == 'yes') { + final encryptedApkamSymmetricKey = + jsonDecode(notification.value!)['encryptedApkamSymmetricKey']; + final apkamSymmetricKey = EncryptionUtil.decryptKey( + encryptedApkamSymmetricKey, atAuthKeys.defaultEncryptionPrivateKey!); + print('decrypted apkam symmetric key: $apkamSymmetricKey'); + var encryptedDefaultPrivateEncKey = EncryptionUtil.encryptValue( + atAuthKeys.defaultEncryptionPrivateKey!, apkamSymmetricKey); + var encryptedDefaultSelfEncKey = EncryptionUtil.encryptValue( + atAuthKeys.defaultSelfEncryptionKey!, apkamSymmetricKey); + enrollParamsJson['encryptedDefaultEncryptedPrivateKey'] = + encryptedDefaultPrivateEncKey; + enrollParamsJson['encryptedDefaultSelfEncryptionKey'] = + encryptedDefaultSelfEncKey; + enrollRequest = 'enroll:approve:${jsonEncode(enrollParamsJson)}\n'; + } else { + enrollRequest = 'enroll:deny:${jsonEncode(enrollParamsJson)}\n'; + } + print('enroll request to server: $enrollRequest'); + String? enrollResponse = await atClient + .getRemoteSecondary()! + .executeCommand(enrollRequest, auth: true); + print('enrollResponse: $enrollResponse'); +} + +AtAuthKeys _decryptAtKeysFile(Map jsonData) { + var securityKeys = AtAuthKeys(); + String decryptionKey = jsonData[auth_constants.defaultSelfEncryptionKey]!; + var atChops = + AtChopsImpl(AtChopsKeys()..selfEncryptionKey = AESKey(decryptionKey)); + securityKeys.defaultEncryptionPublicKey = atChops + .decryptString(jsonData[auth_constants.defaultEncryptionPublicKey]!, + EncryptionKeyType.aes256, + keyName: 'selfEncryptionKey', iv: AtChopsUtil.generateIVLegacy()) + .result; + securityKeys.defaultEncryptionPrivateKey = atChops + .decryptString(jsonData[auth_constants.defaultEncryptionPrivateKey]!, + EncryptionKeyType.aes256, + keyName: 'selfEncryptionKey', iv: AtChopsUtil.generateIVLegacy()) + .result; + securityKeys.defaultSelfEncryptionKey = decryptionKey; + securityKeys.apkamPublicKey = atChops + .decryptString( + jsonData[auth_constants.apkamPublicKey]!, EncryptionKeyType.aes256, + keyName: 'selfEncryptionKey', iv: AtChopsUtil.generateIVLegacy()) + .result; + securityKeys.apkamPrivateKey = atChops + .decryptString( + jsonData[auth_constants.apkamPrivateKey]!, EncryptionKeyType.aes256, + keyName: 'selfEncryptionKey', iv: AtChopsUtil.generateIVLegacy()) + .result; + securityKeys.apkamSymmetricKey = jsonData[auth_constants.apkamSymmetricKey]; + securityKeys.enrollmentId = jsonData[AtConstants.enrollmentId]; + return securityKeys; +} + +Future> _readAtKeysFile(String? atKeysFilePath) async { + if (atKeysFilePath == null || atKeysFilePath.isEmpty) { + throw AtException( + 'atKeys filePath is empty. atKeysFile is required to authenticate'); + } + if (!File(atKeysFilePath).existsSync()) { + throw AtException( + 'provided keys file does not exist. Please check whether the file path $atKeysFilePath is valid'); + } + String atAuthData = await File(atKeysFilePath).readAsString(); + Map jsonData = {}; + json.decode(atAuthData).forEach((String key, dynamic value) { + jsonData[key] = value.toString(); + }); + return jsonData; +} + +AtChops _createAtChops(AtAuthKeys atKeysFile) { + final atEncryptionKeyPair = AtEncryptionKeyPair.create( + atKeysFile.defaultEncryptionPublicKey!, + atKeysFile.defaultEncryptionPrivateKey!); + final atPkamKeyPair = AtPkamKeyPair.create( + atKeysFile.apkamPublicKey!, atKeysFile.apkamPrivateKey!); + final atChopsKeys = AtChopsKeys.create(atEncryptionKeyPair, atPkamKeyPair); + if (atKeysFile.apkamSymmetricKey != null) { + atChopsKeys.apkamSymmetricKey = AESKey(atKeysFile.apkamSymmetricKey!); + } + atChopsKeys.selfEncryptionKey = AESKey(atKeysFile.defaultSelfEncryptionKey!); + return AtChopsImpl(atChopsKeys); +} diff --git a/packages/at_onboarding_cli/example/onboard.dart b/packages/at_onboarding_cli/example/onboard.dart index dcec9787..be72b07d 100644 --- a/packages/at_onboarding_cli/example/onboard.dart +++ b/packages/at_onboarding_cli/example/onboard.dart @@ -1,15 +1,15 @@ import 'package:at_onboarding_cli/at_onboarding_cli.dart'; import 'package:at_utils/at_logger.dart'; -Future main() async { +Future main(List args) async { AtSignLogger.root_level = 'finest'; - final atSign = '@aliceđź› '; + final atSign = args[0]; AtOnboardingPreference atOnboardingPreference = AtOnboardingPreference() ..namespace = 'wavi' // unique identifier that can be used to identify data from your app ..cramSecret = 'b26455a907582760ebf35bc4847de549bc41c24b25c8b1c58d5964f7b4f8a43bc55b0e9a601c9a9657d9a8b8bbc32f88b4e38ffaca03c8710ebae1b14ca9f364' - ..atKeysFilePath = '/home/murali/.atsign/@aliceđź› _key.atKeys' + ..atKeysFilePath = args[1] ..appName = 'wavi' ..deviceName = 'pixel' ..rootDomain = 'vip.ve.atsign.zone' diff --git a/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service_impl.dart b/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service_impl.dart index 0e1065be..aa5d5dd4 100644 --- a/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service_impl.dart +++ b/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service_impl.dart @@ -48,9 +48,10 @@ class AtOnboardingServiceImpl implements AtOnboardingService { // set default LocalStorage paths for this instance atOnboardingPreference.commitLogPath ??= - HomeDirectoryUtil.getCommitLogPath(_atSign); + HomeDirectoryUtil.getCommitLogPath(_atSign, enrollmentId: enrollmentId); atOnboardingPreference.hiveStoragePath ??= - HomeDirectoryUtil.getHiveStoragePath(_atSign); + HomeDirectoryUtil.getHiveStoragePath(_atSign, + enrollmentId: enrollmentId); atOnboardingPreference.isLocalStoreRequired = true; atOnboardingPreference.atKeysFilePath ??= HomeDirectoryUtil.getAtKeysPath(_atSign); diff --git a/packages/at_onboarding_cli/lib/src/util/home_directory_util.dart b/packages/at_onboarding_cli/lib/src/util/home_directory_util.dart index 754415f7..70add5e5 100644 --- a/packages/at_onboarding_cli/lib/src/util/home_directory_util.dart +++ b/packages/at_onboarding_cli/lib/src/util/home_directory_util.dart @@ -27,19 +27,27 @@ class HomeDirectoryUtil { return path.join(homeDir!, '.atsign', 'keys', '${atsign}_key.atKeys'); } - static String getStorageDirectory(String atsign) { + static String getStorageDirectory(String atsign, {String? enrollmentId}) { if (homeDir == null) { throw AtClientException.message('Could not find home directory'); } + if (enrollmentId != null) { + return path.join(homeDir!, '.atsign', 'at_onboarding_cli', 'storage', + atsign, enrollmentId); + } return path.join( homeDir!, '.atsign', 'at_onboarding_cli', 'storage', atsign); } - static String getCommitLogPath(String atsign) { - return path.join(getStorageDirectory(atsign), 'commitLog'); + static String getCommitLogPath(String atsign, {String? enrollmentId}) { + return path.join( + getStorageDirectory(atsign, enrollmentId: enrollmentId), 'commitLog'); } - static String getHiveStoragePath(String atsign) { - return path.join(HomeDirectoryUtil.getStorageDirectory(atsign), 'hive'); + static String getHiveStoragePath(String atsign, {String? enrollmentId}) { + return path.join( + HomeDirectoryUtil.getStorageDirectory(atsign, + enrollmentId: enrollmentId), + 'hive'); } } diff --git a/packages/at_onboarding_cli/pubspec.yaml b/packages/at_onboarding_cli/pubspec.yaml index d1a22a41..0d867802 100644 --- a/packages/at_onboarding_cli/pubspec.yaml +++ b/packages/at_onboarding_cli/pubspec.yaml @@ -28,6 +28,14 @@ dependencies: at_server_status: ^1.0.3 at_utils: ^3.0.15 +dependency_overrides: + at_client: + git: + url: https://github.com/atsign-foundation/at_client_sdk/ + path: packages/at_client + ref: enrollmentid_bug + + dev_dependencies: lints: ^2.1.0 test: ^1.24.2