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: Apkam onboarding changes #399

Closed
wants to merge 38 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
6dab0a1
feat: apkam onboarding first app
murali-shris Aug 14, 2023
964a166
feat: apkam enroll in onboarding cli
murali-shris Aug 17, 2023
b5c6cef
fix: code refactoring
murali-shris Aug 17, 2023
a8885d1
feat: add enrollresponse and status to at_commons
murali-shris Aug 20, 2023
6fb2dcd
Merge branch 'trunk' of https://github.com/atsign-foundation/at_libra…
murali-shris Aug 25, 2023
6acd487
feat: functional tests and bug fixes for apkam onboarding
murali-shris Aug 29, 2023
fce9570
fix: analyzer issue and func test failure fix
murali-shris Aug 29, 2023
ae06b94
fix: remove pkam load for functional test, manually create keys insid…
murali-shris Aug 29, 2023
98e45e3
fix: comment check_test_env readiness
murali-shris Aug 29, 2023
ae07f31
fix: change atsign for onboard test
murali-shris Aug 29, 2023
a9d0ab1
fix: added onboarding functional tests readme, minor changes
murali-shris Aug 30, 2023
e4dee07
added denial test and minor change
purnimavenkatasubbu Aug 31, 2023
56295f2
Merge branch 'trunk' into apkam_onboarding_changes
murali-shris Sep 7, 2023
bc10f21
fix: change totp to otp
murali-shris Sep 7, 2023
57f3f69
fix: upgrade dependency in func test pubspec
murali-shris Sep 7, 2023
be40e9a
fix: change totp to otp in tests
murali-shris Sep 7, 2023
0c31261
fix: remove dependency overrides for at_client and replace with versi…
murali-shris Sep 14, 2023
b45a246
fix: replace at_client dependency overrides in func tests pubspec wit…
murali-shris Sep 14, 2023
41f8b5c
fix: code comment, reduced timeout in func test from 10 to 5 mins
murali-shris Sep 14, 2023
acc2923
fix: changes for backward compatibility
murali-shris Sep 15, 2023
6838c9e
fix: functional test issue fix and add error code change for apkam au…
murali-shris Sep 15, 2023
9a30de9
added tests
purnimavenkatasubbu Sep 15, 2023
b90f09c
Merge branch 'trunk' into apkam_onboarding_changes
murali-shris Sep 15, 2023
beaccd8
feat: replace onboard impl with at_auth.onboard
murali-shris Oct 9, 2023
def786a
fix: generate keys file after onboard
murali-shris Oct 10, 2023
23f5e20
fix: auth in onboarding service replaced with new package at_auth
murali-shris Oct 10, 2023
0d6ce72
fix: modify unit tests
murali-shris Oct 12, 2023
cda9a8d
fix: dart analyzer issues
murali-shris Oct 12, 2023
1ae6f25
fix: set useAtChops=true in functional test
murali-shris Oct 12, 2023
c85c0bd
fix: replace pubspec with new branches for at_auth and at_chops
murali-shris Oct 13, 2023
9fc1e29
fix: update functional tests pubspec with latest branch
murali-shris Oct 13, 2023
515a20b
fix: replace pubspec with published version of at_chops and at_auth
murali-shris Oct 13, 2023
87a0efb
fix: replace at_chops and at_auth with published versions in pubspec
murali-shris Oct 13, 2023
d6e7f8b
build: at_client latest trunk changes
murali-shris Oct 24, 2023
5b07733
build: replace at_client trunk with published version
murali-shris Oct 24, 2023
1fcd19d
feat: modified examples for apkam enrollment
murali-shris Oct 26, 2023
dd9d1b7
fix: checkin missed file
murali-shris Oct 26, 2023
f00c650
fix: functional test issue
murali-shris Oct 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .github/workflows/at_libraries.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,13 @@ jobs:
working-directory: tests/${{ matrix.package }}
run: dart run check_docker_readiness.dart

- name: run pkamLoad on docker-image
run: sudo docker exec at_onboarding_cli_functional_tests_virtualenv_1 supervisorctl start pkamLoad
# commented since onboarding tests throw atsign already activated exception
# - name: run pkamLoad on docker-image
# run: sudo docker exec at_onboarding_cli_functional_tests_virtualenv_1 supervisorctl start pkamLoad

- name: Check test environment readiness
working-directory: tests/${{ matrix.package }}
run: dart run check_test_env.dart
# - name: Check test environment readiness
# working-directory: tests/${{ matrix.package }}
# run: dart run check_test_env.dart

- name: run tests
working-directory: tests/${{ matrix.package }}
Expand Down
30 changes: 30 additions & 0 deletions packages/at_onboarding_cli/example/README.md
Original file line number Diff line number Diff line change
@@ -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 <atsign> <path_store_keys_file>
e.g. dart example/onboard.dart @alice🛠 /home/alice/.atsign/@alice🛠_wavikey.atKeys
2. Authenticate using the onboarded atsign
dart example/apkam_authenticate.dart <atsign> <path_of_keys_file_from_#1>
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 <atsign> <path_of_keys_file_from_#1>
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>:<pkamSignature>
enrollmentId - get from the .atKeys file
pkamChallenge - generate using the below commnd
at_tools/packages/at_pkam>
dart bin/main.dart -p <keys_file_path> <from_response>
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 <atsign> <path_to_store_keys_file> <otp>
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 <atsign> <path_of_keys_file_from_#5.1>


32 changes: 32 additions & 0 deletions packages/at_onboarding_cli/example/apkam_authenticate.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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<void> main(List<String> 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 = args[1]
..rootDomain = 'vip.ve.atsign.zone';
AtOnboardingService? onboardingService = AtOnboardingServiceImpl(
atSign, atOnboardingPreference,
enrollmentId: _getEnrollmentIdFromKeysFile(args[1]));
await onboardingService.authenticate(); // when authenticating
// AtLookUp? atLookup = onboardingService.atLookUp;
// AtClient? client = onboardingService.atClient;
// print(await client?.getKeys());
// 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;
}
33 changes: 33 additions & 0 deletions packages/at_onboarding_cli/example/apkam_enroll.dart
Original file line number Diff line number Diff line change
@@ -0,0 +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<void> main(List<String> args) async {
AtSignLogger.root_level = 'finer';
final atSign = args[0];
AtOnboardingPreference atOnboardingPreference = AtOnboardingPreference()
..namespace =
'buzz' // unique identifier that can be used to identify data from your app
..atKeysFilePath = args[1]
..appName = 'buzz'
..deviceName = 'iphone'
..rootDomain = 'vip.ve.atsign.zone'
..apkamAuthRetryDurationMins = 3;
AtOnboardingService? onboardingService =
AtOnboardingServiceImpl(atSign, atOnboardingPreference);
Map<String, String> namespaces = {"buzz": "rw"};
// run totp:get from enrolled client and pass the otp
var enrollmentResponse =
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;
}
34 changes: 34 additions & 0 deletions packages/at_onboarding_cli/example/atsign_preference.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:at_client/src/preference/at_client_preference.dart';
import 'package:at_onboarding_cli/src/util/home_directory_util.dart';
import 'dart:io';
import 'package:crypto/crypto.dart';

class AtSignPreference {
static AtClientPreference getAlicePreference(
String atSign, String enrollmentId) {
var preference = AtClientPreference();
preference.hiveStoragePath = HomeDirectoryUtil.getHiveStoragePath(atSign,
enrollmentId: enrollmentId);
preference.commitLogPath =
HomeDirectoryUtil.getCommitLogPath(atSign, enrollmentId: enrollmentId);
preference.isLocalStoreRequired = true;
preference.rootDomain = 'vip.ve.atsign.zone';
var hashFile = _getShaForAtSign(atSign);
preference.keyStoreSecret =
_getKeyStoreSecret('${preference.hiveStoragePath}/$hashFile.hash');
return preference;
}

static List<int> _getKeyStoreSecret(String filePath) {
var hiveSecretString = File(filePath).readAsStringSync();
var secretAsUint8List = Uint8List.fromList(hiveSecretString.codeUnits);
return secretAsUint8List;
}

static String _getShaForAtSign(String atsign) {
var bytes = utf8.encode(atsign);
return sha256.convert(bytes).toString();
}
}
137 changes: 137 additions & 0 deletions packages/at_onboarding_cli/example/enroll_app_listen.dart
Original file line number Diff line number Diff line change
@@ -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 <atsign> <path_to_key_file>
void main(List<String> 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<void> _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<String, String> 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<Map<String, String>> _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<String, String> jsonData = <String, String>{};
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);
}
20 changes: 20 additions & 0 deletions packages/at_onboarding_cli/example/onboard.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:at_onboarding_cli/at_onboarding_cli.dart';
import 'package:at_utils/at_logger.dart';

Future<void> main(List<String> args) async {
AtSignLogger.root_level = 'finest';
final atSign = args[0];
AtOnboardingPreference atOnboardingPreference = AtOnboardingPreference()
..namespace =
'wavi' // unique identifier that can be used to identify data from your app
..cramSecret =
'b26455a907582760ebf35bc4847de549bc41c24b25c8b1c58d5964f7b4f8a43bc55b0e9a601c9a9657d9a8b8bbc32f88b4e38ffaca03c8710ebae1b14ca9f364'
..atKeysFilePath = args[1]
..appName = 'wavi'
..deviceName = 'pixel'
..rootDomain = 'vip.ve.atsign.zone'
..enableEnrollmentDuringOnboard = true;
AtOnboardingService? onboardingService =
AtOnboardingServiceImpl(atSign, atOnboardingPreference);
await onboardingService.onboard();
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import 'package:at_chops/at_chops.dart';
import 'package:at_client/at_client.dart';
import 'package:at_lookup/at_lookup.dart';
import 'package:at_auth/at_auth.dart';
import 'package:at_onboarding_cli/at_onboarding_cli.dart';

abstract class AtOnboardingService {
///perform initial one_time authentication to activate the atsign
///returns true if onboarded
Future<bool> onboard();

///authenticate into secondary server using privateKey
///Authenticate into secondary server using PKAM privateKey for legacy clients
///For clients that are enrolled through APKAM, pass the enrollmentId and auth is done using APKAM private key
///returns true if authenticated
Future<bool> authenticate();
Future<bool> authenticate({String? enrollmentId});

/// Sends an enroll request to the server. Apps that are already enrolled will receive notifications for this enroll request and can approve/deny the request
/// appName - application name of the client e.g wavi,buzz, atmosphere etc.,
/// deviceName - device identifier from the requesting application e.g iphone,any unique ID that identifies the requesting client
/// otp - otp retrieved from an already enrolled app
/// namespaces - key-value pair of namespace-access of the requesting client e.g {"wavi":"rw","contacts":"r"}
/// pkamRetryIntervalMins - optional param which specifies interval in mins for pkam retry for this enrollment.
/// The passed value will override the value in [AtOnboardingPreference]
Future<EnrollResponse> enroll(String appName, String deviceName, String otp,
Map<String, String> namespaces,
{int? pkamRetryIntervalMins});

///returns an authenticated instance of AtClient
@Deprecated('use getter')
Expand All @@ -36,4 +50,8 @@ abstract class AtOnboardingService {
set atChops(AtChops? atChops);

AtChops? get atChops;

set atAuth(AtAuth? atAuth);

AtAuth? get atAuth;
}
Loading