From 6dab0a17a98cb814c213af34f06ee87c32a5656b Mon Sep 17 00:00:00 2001 From: murali-shris Date: Mon, 14 Aug 2023 12:45:19 +0530 Subject: [PATCH 01/35] feat: apkam onboarding first app --- .../lib/src/key/impl/at_chops_keys.dart | 9 + packages/at_lookup/lib/src/at_lookup.dart | 3 +- .../at_lookup/lib/src/at_lookup_impl.dart | 13 +- packages/at_lookup/pubspec.yaml | 4 +- .../onboard/at_onboarding_service_impl.dart | 182 ++++++++++++------ .../lib/src/onboard/at_security_keys.dart | 12 ++ .../src/util/at_onboarding_preference.dart | 4 + .../lib/src/util/auth_key_type.dart | 1 + packages/at_onboarding_cli/pubspec.yaml | 7 + 9 files changed, 167 insertions(+), 68 deletions(-) create mode 100644 packages/at_onboarding_cli/lib/src/onboard/at_security_keys.dart diff --git a/packages/at_chops/lib/src/key/impl/at_chops_keys.dart b/packages/at_chops/lib/src/key/impl/at_chops_keys.dart index d0fcf80d..ee2ebf5d 100644 --- a/packages/at_chops/lib/src/key/impl/at_chops_keys.dart +++ b/packages/at_chops/lib/src/key/impl/at_chops_keys.dart @@ -8,10 +8,19 @@ class AtChopsKeys { AtPkamKeyPair? _atPkamKeyPair; AtSigningKeyPair? atSigningKeyPair; + + @Deprecated('Use selfEncryptionKey') SymmetricKey? symmetricKey; + SymmetricKey? selfEncryptionKey; + + SymmetricKey? apkamSymmetricKey; + AtChopsKeys.create(this.atEncryptionKeyPair, this._atPkamKeyPair); + AtChopsKeys(); + + @Deprecated('Use selfEncryptionKey') AtChopsKeys.createSymmetric(this.symmetricKey); AtPkamKeyPair? get atPkamKeyPair => _atPkamKeyPair; diff --git a/packages/at_lookup/lib/src/at_lookup.dart b/packages/at_lookup/lib/src/at_lookup.dart index 631a9880..425c7c95 100644 --- a/packages/at_lookup/lib/src/at_lookup.dart +++ b/packages/at_lookup/lib/src/at_lookup.dart @@ -28,7 +28,8 @@ abstract class AtLookUp { /// performs a PKAM authentication using private key on the client side and public key on secondary server /// Default signing algorithm for pkam signature is [SigningAlgoType.rsa2048] and default hashing algorithm is [HashingAlgoType.sha256] - Future pkamAuthenticate(); + /// Optionally pass enrollmentId is the client is enrolled using APKAM + Future pkamAuthenticate({String? enrollmentId}); /// set an instance of [AtChops] for signing and verification operations. set atChops(AtChops? atChops); diff --git a/packages/at_lookup/lib/src/at_lookup_impl.dart b/packages/at_lookup/lib/src/at_lookup_impl.dart index ca777781..ae4d5570 100644 --- a/packages/at_lookup/lib/src/at_lookup_impl.dart +++ b/packages/at_lookup/lib/src/at_lookup_impl.dart @@ -433,7 +433,7 @@ class AtLookupImpl implements AtLookUp { } @override - Future pkamAuthenticate() async { + Future pkamAuthenticate({String? enrollmentId}) async { await createConnection(); try { await _pkamAuthenticationMutex.acquire(); @@ -456,10 +456,13 @@ class AtLookupImpl implements AtLookUp { ..hashingAlgoType = hashingAlgoType ..signingMode = AtSigningMode.pkam; var signingResult = _atChops!.sign(atSigningInput); - var pkamCommand = - 'pkam:signingAlgo:${signingAlgoType.name}:hashingAlgo:${hashingAlgoType.name}:${signingResult.result}\n'; - logger.finer('pkamCommand:$pkamCommand'); - await _sendCommand(pkamCommand); + var pkamBuilder = PkamVerbBuilder() + ..signingAlgo = signingAlgoType.name + ..hashingAlgo = hashingAlgoType.name + ..enrollmentlId = enrollmentId + ..signature = signingResult.result; + logger.finer('pkamCommand:${pkamBuilder.buildCommand()}'); + await _sendCommand(pkamBuilder.buildCommand()); var pkamResponse = await messageListener.read(); if (pkamResponse == 'data:success') { diff --git a/packages/at_lookup/pubspec.yaml b/packages/at_lookup/pubspec.yaml index ecd68934..d8eef841 100644 --- a/packages/at_lookup/pubspec.yaml +++ b/packages/at_lookup/pubspec.yaml @@ -12,8 +12,8 @@ dependencies: path: ^1.8.0 crypton: ^2.0.1 crypto: ^3.0.1 - at_utils: ^3.0.12 - at_commons: ^3.0.43 + at_utils: ^3.0.15 + at_commons: ^3.0.53 mutex: ^3.0.0 mocktail: ^0.3.0 meta: ^1.8.0 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 58fa7bf7..f7d00044 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 @@ -5,10 +5,12 @@ import 'dart:io'; import 'package:at_chops/at_chops.dart'; import 'package:at_client/at_client.dart'; +import 'package:at_onboarding_cli/src/onboard/at_security_keys.dart'; import 'package:at_onboarding_cli/src/util/at_onboarding_exceptions.dart'; import 'package:at_server_status/at_server_status.dart'; import 'package:at_utils/at_utils.dart'; import 'package:at_commons/at_builders.dart'; +import 'package:at_commons/at_commons.dart'; import 'package:at_onboarding_cli/src/factory/service_factories.dart'; import 'package:at_lookup/at_lookup.dart'; import 'package:at_onboarding_cli/at_onboarding_cli.dart'; @@ -67,8 +69,8 @@ class AtOnboardingServiceImpl implements AtOnboardingService { _atClient ??= atClientManager.atClient; } - Future _init(Map atKeysFileDataMap) async { - atChops ??= _createAtChops(atKeysFileDataMap); + Future _init(AtSecurityKeys atKeysFile) async { + atChops ??= _createAtChops(atKeysFile); await _initAtClient(atChops!); _atLookUp!.atChops = atChops; _atClient!.atChops = atChops; @@ -83,6 +85,11 @@ class AtOnboardingServiceImpl implements AtOnboardingService { @override Future onboard() async { + if (atOnboardingPreference.appName == null || + atOnboardingPreference.deviceName == null) { + throw AtOnboardingException( + 'appName and deviceName are mandatory for onboarding'); + } // cram auth doesn't use at_chops. So create at_lookup here. AtLookupImpl atLookUpImpl = AtLookupImpl(_atSign, atOnboardingPreference.rootDomain, atOnboardingPreference.rootPort); @@ -127,8 +134,8 @@ class AtOnboardingServiceImpl implements AtOnboardingService { ' and try again \n(or) contact support@atsign.com'); } logger.severe('Caught exception: $e'); - } on Error catch (e) { - logger.severe('Caught error: $e'); + } on Error catch (e, trace) { + logger.severe('Caught error: $e $trace'); } finally { await atLookUpImpl.close(); } @@ -137,116 +144,168 @@ class AtOnboardingServiceImpl implements AtOnboardingService { ///method to generate/update encryption key-pairs to activate an atsign Future _activateAtsign(AtLookupImpl atLookUpImpl) async { - //1. Generate pkam key pair(if authMode is keyFile), encryption key pair and self encryption key - Map atKeysMap = await _generateKeyPairs(); - - //2. generate .atKeys file using a copy of atKeysMap - await _generateAtKeysFile(Map.of(atKeysMap)); + //1. Generate pkam key pair(if authMode is keyFile), encryption key pair, self encryption key and apkam symmetric key pair + AtSecurityKeys atSecurityKeys = await _generateKeyPairs(); + + var enrollBuilder = EnrollVerbBuilder() + ..appName = atOnboardingPreference.appName + ..deviceName = atOnboardingPreference.deviceName; + + // #TODO replace encryption util methods with at_chops methods when refactoring + enrollBuilder.encryptedDefaultEncryptedPrivateKey = + EncryptionUtil.encryptValue(atSecurityKeys.defaultEncryptionPrivateKey!, + atSecurityKeys.apkamSymmetricKey!); + enrollBuilder.encryptedDefaultSelfEncryptionKey = + EncryptionUtil.encryptValue(atSecurityKeys.defaultSelfEncryptionKey!, + atSecurityKeys.apkamSymmetricKey!); + enrollBuilder.apkamPublicKey = atSecurityKeys.apkamPublicKey; + print(enrollBuilder.buildCommand()); + + //2. Send enroll request to server + var enrollResult = await atLookUpImpl + .executeCommand(enrollBuilder.buildCommand(), auth: false); + if (enrollResult == null || enrollResult.isEmpty) { + throw AtOnboardingException('Enrollment response is null or empty'); + } else if (enrollResult.startsWith('error:')) { + throw AtOnboardingException('Enrollment error:$enrollResult'); + } + enrollResult = enrollResult.replaceFirst('data:', ''); + print('***enrollResult: $enrollResult'); + var enrollResultJson = jsonDecode(enrollResult); + var enrollmentIdFromServer = enrollResultJson[enrollmentId]; + var enrollmentStatus = enrollResultJson['status']; + if (enrollmentStatus != 'approved') { + throw AtOnboardingException( + 'initial enrollment is not approved. Status from server: $enrollmentStatus'); + } + atSecurityKeys.enrollmentId = enrollmentIdFromServer; - //3. Updating pkamPublicKey to remote secondary - logger.finer('Updating PkamPublicKey to remote secondary'); - final pkamPublicKey = atKeysMap[AuthKeyType.pkamPublicKey]; - String updateCommand = 'update:$AT_PKAM_PUBLIC_KEY $pkamPublicKey\n'; - String? pkamUpdateResult = - await atLookUpImpl.executeCommand(updateCommand, auth: false); - logger.info('PkamPublicKey update result: $pkamUpdateResult'); + //3. Close connection to server + try { + atLookUpImpl.close(); + } on Exception catch (e) { + logger.severe('error while closing connection to server: $e'); + } //4. initialise atClient and atChops and attempt a pkam auth to server. - await _init(atKeysMap); - _isPkamAuthenticated = (await _atLookUp?.pkamAuthenticate())!; + await _init(atSecurityKeys); + + //4. create new connection to server and do pkam with enrollmentId + try { + _isPkamAuthenticated = await _atLookUp! + .pkamAuthenticate(enrollmentId: enrollmentIdFromServer); + } on UnAuthenticatedException { + throw AtOnboardingException( + 'Pkam auth with enrollmentId-$enrollmentIdFromServer failed'); + } + if (!_isPkamAuthenticated) { + throw AtOnboardingException( + 'Pkam auth with enrollmentId-$enrollmentIdFromServer failed'); + } + print('*** _isPkamAuthenticated:$_isPkamAuthenticated'); + //2. generate .atKeys file using a copy of atKeysMap + + // _isPkamAuthenticated = (await _atLookUp?.pkamAuthenticate())!; //5. If Pkam auth is success, update encryption public key to secondary and delete cram key from server if (_isPkamAuthenticated) { - final encryptionPublicKey = atKeysMap[AuthKeyType.encryptionPublicKey]; + final encryptionPublicKey = atSecurityKeys.defaultEncryptionPublicKey; UpdateVerbBuilder updateBuilder = UpdateVerbBuilder() ..atKey = 'publickey' ..isPublic = true ..value = encryptionPublicKey ..sharedBy = _atSign; String? encryptKeyUpdateResult = - await atLookUpImpl.executeVerb(updateBuilder); + await _atLookUp!.executeVerb(updateBuilder); logger .info('Encryption public key update result $encryptKeyUpdateResult'); // deleting cram secret from the keystore as cram auth is complete DeleteVerbBuilder deleteBuilder = DeleteVerbBuilder() ..atKey = AT_CRAM_SECRET; - String? deleteResponse = await atLookUpImpl.executeVerb(deleteBuilder); + String? deleteResponse = await _atLookUp!.executeVerb(deleteBuilder); logger.info('Cram secret delete response : $deleteResponse'); //displays status of the atsign logger.finer(await getServerStatus()); stdout.writeln('[Success]----------atSign activated---------'); - } else { - throw UnAuthenticatedException('Unable to authenticate.' - ' Please provide a valid keys file'); + stdout.writeln('-----------------saving atkeys file---------'); + await _generateAtKeysFile(enrollmentIdFromServer, atSecurityKeys); } } - Future> _generateKeyPairs() async { + AtSecurityKeys _generateKeyPairs() { // generate user encryption keypair logger.info('Generating encryption keypair'); var encryptionKeyPair = generateRsaKeypair(); //generate selfEncryptionKey var selfEncryptionKey = generateAESKey(); - + var apkamSymmetricKey = generateAESKey(); + var atKeysFile = AtSecurityKeys(); stdout.writeln( '[Information] Generating your encryption keys and .atKeys file\n'); - //mapping encryption keys pairs to their names - Map atKeysMap = {}; - String pkamPublicKey; + late String apkamPublicKey; //generating pkamKeyPair only if authMode is keysFile if (atOnboardingPreference.authMode == PkamAuthMode.keysFile) { logger.info('Generating pkam keypair'); - var pkamRsaKeypair = generateRsaKeypair(); - atKeysMap[AuthKeyType.pkamPublicKey] = - pkamRsaKeypair.publicKey.toString(); - atKeysMap[AuthKeyType.pkamPrivateKey] = - pkamRsaKeypair.privateKey.toString(); - pkamPublicKey = pkamRsaKeypair.publicKey.toString(); + var apkamRsaKeypair = generateRsaKeypair(); + atKeysFile.apkamPublicKey = apkamRsaKeypair.publicKey.toString(); + atKeysFile.apkamPrivateKey = apkamRsaKeypair.privateKey.toString(); + print('apkamPrivateKey:${atKeysFile.apkamPrivateKey}'); + apkamPublicKey = apkamRsaKeypair.publicKey.toString(); } else if (atOnboardingPreference.authMode == PkamAuthMode.sim) { // get the public key from secure element - pkamPublicKey = + apkamPublicKey = atChops!.readPublicKey(atOnboardingPreference.publicKeyId!); - logger.info('pkam public key from sim: $pkamPublicKey'); - atKeysMap[AuthKeyType.pkamPublicKey] = pkamPublicKey; + logger.info('pkam public key from sim: $apkamPublicKey'); + // encryption key pair and self encryption symmetric key // are not available to injected at_chops. Set it here atChops!.atChopsKeys.atEncryptionKeyPair = AtEncryptionKeyPair.create( encryptionKeyPair.publicKey.toString(), encryptionKeyPair.privateKey.toString()); - atChops!.atChopsKeys.symmetricKey = AESKey(selfEncryptionKey); + atChops!.atChopsKeys.selfEncryptionKey = AESKey(selfEncryptionKey); + atChops!.atChopsKeys.apkamSymmetricKey = AESKey(apkamSymmetricKey); } + atKeysFile.apkamPublicKey = apkamPublicKey; //Standard order of an atKeys file is -> - // pkam keypair -> encryption keypair -> selfEncryption key -> + // pkam keypair -> encryption keypair -> selfEncryption key -> enrollmentId --> apkam symmetric key --> // @sign: selfEncryptionKey[self encryption key again] // note: "->" stands for "followed by" - atKeysMap[AuthKeyType.encryptionPublicKey] = + atKeysFile.defaultEncryptionPublicKey = encryptionKeyPair.publicKey.toString(); - atKeysMap[AuthKeyType.encryptionPrivateKey] = + atKeysFile.defaultEncryptionPrivateKey = encryptionKeyPair.privateKey.toString(); - atKeysMap[AuthKeyType.selfEncryptionKey] = selfEncryptionKey; - atKeysMap[_atSign] = selfEncryptionKey; + atKeysFile.defaultSelfEncryptionKey = selfEncryptionKey; + atKeysFile.apkamSymmetricKey = apkamSymmetricKey; - return atKeysMap; + return atKeysFile; } ///write newly created encryption keypairs into atKeys file - Future _generateAtKeysFile(Map atKeysMap) async { + Future _generateAtKeysFile( + String currentEnrollmentId, AtSecurityKeys atSecurityKeys) async { + Map atKeysMap = {}; //encrypting all keys with self encryption key if (atOnboardingPreference.authMode == PkamAuthMode.keysFile) { atKeysMap[AuthKeyType.pkamPrivateKey] = EncryptionUtil.encryptValue( - atKeysMap[AuthKeyType.pkamPrivateKey]!, - atKeysMap[AuthKeyType.selfEncryptionKey]!); + atSecurityKeys.apkamPrivateKey!, + atSecurityKeys.defaultSelfEncryptionKey!); } atKeysMap[AuthKeyType.pkamPublicKey] = EncryptionUtil.encryptValue( - atKeysMap[AuthKeyType.pkamPublicKey]!, - atKeysMap[AuthKeyType.selfEncryptionKey]!); + atSecurityKeys.apkamPublicKey!, + atSecurityKeys.defaultSelfEncryptionKey!); atKeysMap[AuthKeyType.encryptionPublicKey] = EncryptionUtil.encryptValue( - atKeysMap[AuthKeyType.encryptionPublicKey]!, - atKeysMap[AuthKeyType.selfEncryptionKey]!); + atSecurityKeys.defaultEncryptionPublicKey!, + atSecurityKeys.defaultSelfEncryptionKey!); atKeysMap[AuthKeyType.encryptionPrivateKey] = EncryptionUtil.encryptValue( - atKeysMap[AuthKeyType.encryptionPrivateKey]!, - atKeysMap[AuthKeyType.selfEncryptionKey]!); + atSecurityKeys.defaultEncryptionPrivateKey!, + atSecurityKeys.defaultSelfEncryptionKey!); + atKeysMap[AuthKeyType.selfEncryptionKey] = + atSecurityKeys.defaultSelfEncryptionKey!; + atKeysMap[_atSign] = atSecurityKeys.defaultSelfEncryptionKey!; + atKeysMap[AuthKeyType.apkamSymmetricKey] = + atSecurityKeys.apkamSymmetricKey!; + atKeysMap['enrollmentId'] = currentEnrollmentId; if (!atOnboardingPreference.atKeysFilePath!.endsWith('.atKeys')) { atOnboardingPreference.atKeysFilePath = @@ -316,7 +375,8 @@ class AtOnboardingServiceImpl implements AtOnboardingService { '${atOnboardingPreference.atKeysFilePath}. Please provide a valid atKeys file', exceptionScenario: ExceptionScenario.invalidValueProvided); } - await _init(atKeysFileDataMap); + // #TODO replace with AtKeysFile + // await _init(atKeysFileDataMap); logger.finer('Authenticating using PKAM'); try { _isPkamAuthenticated = (await _atLookUp?.pkamAuthenticate())!; @@ -335,14 +395,16 @@ class AtOnboardingServiceImpl implements AtOnboardingService { return _isPkamAuthenticated; } - AtChops _createAtChops(Map atKeysDataMap) { + AtChops _createAtChops(AtSecurityKeys atKeysFile) { final atEncryptionKeyPair = AtEncryptionKeyPair.create( - atKeysDataMap[AuthKeyType.encryptionPublicKey]!, - atKeysDataMap[AuthKeyType.encryptionPrivateKey]!); + atKeysFile.defaultEncryptionPublicKey!, + atKeysFile.defaultEncryptionPrivateKey!); final atPkamKeyPair = AtPkamKeyPair.create( - atKeysDataMap[AuthKeyType.pkamPublicKey]!, - atKeysDataMap[AuthKeyType.pkamPrivateKey]!); + atKeysFile.apkamPublicKey!, atKeysFile.apkamPrivateKey!); final atChopsKeys = AtChopsKeys.create(atEncryptionKeyPair, atPkamKeyPair); + atChopsKeys.apkamSymmetricKey = AESKey(atKeysFile.apkamSymmetricKey!); + atChopsKeys.selfEncryptionKey = + AESKey(atKeysFile.defaultSelfEncryptionKey!); return AtChopsImpl(atChopsKeys); } diff --git a/packages/at_onboarding_cli/lib/src/onboard/at_security_keys.dart b/packages/at_onboarding_cli/lib/src/onboard/at_security_keys.dart new file mode 100644 index 00000000..8df3c6d4 --- /dev/null +++ b/packages/at_onboarding_cli/lib/src/onboard/at_security_keys.dart @@ -0,0 +1,12 @@ +/// Holder for different encyrption keys that will be stored in .atKeys file. +/// Apkam symmetric key, enrollmentId and defaultSelfEncryptionKey will be stored in unencrypted format in .atKeys file. +/// All other values will be encrypted before saving to .atKeys file. +class AtSecurityKeys { + String? apkamPublicKey; + String? apkamPrivateKey; + String? defaultEncryptionPublicKey; + String? defaultEncryptionPrivateKey; + String? defaultSelfEncryptionKey; + String? apkamSymmetricKey; + String? enrollmentId; +} diff --git a/packages/at_onboarding_cli/lib/src/util/at_onboarding_preference.dart b/packages/at_onboarding_cli/lib/src/util/at_onboarding_preference.dart index c696c8ce..72438bc9 100644 --- a/packages/at_onboarding_cli/lib/src/util/at_onboarding_preference.dart +++ b/packages/at_onboarding_cli/lib/src/util/at_onboarding_preference.dart @@ -27,4 +27,8 @@ class AtOnboardingPreference extends AtClientPreference { /// the hostName of the registrar which will be used to activate the atsign String registrarUrl = RegistrarApiConstants.apiHostProd; + + late String appName; + + late String deviceName; } diff --git a/packages/at_onboarding_cli/lib/src/util/auth_key_type.dart b/packages/at_onboarding_cli/lib/src/util/auth_key_type.dart index bd1197e3..c465a103 100644 --- a/packages/at_onboarding_cli/lib/src/util/auth_key_type.dart +++ b/packages/at_onboarding_cli/lib/src/util/auth_key_type.dart @@ -4,4 +4,5 @@ class AuthKeyType { static const String encryptionPublicKey = 'aesEncryptPublicKey'; static const String encryptionPrivateKey = 'aesEncryptPrivateKey'; static const String selfEncryptionKey = 'selfEncryptionKey'; + static const String apkamSymmetricKey = 'apkamSymmetricKey'; } diff --git a/packages/at_onboarding_cli/pubspec.yaml b/packages/at_onboarding_cli/pubspec.yaml index dc65a1fd..5258b85a 100644 --- a/packages/at_onboarding_cli/pubspec.yaml +++ b/packages/at_onboarding_cli/pubspec.yaml @@ -27,6 +27,13 @@ dependencies: http: ^0.13.6 at_chops: ^1.0.3 +dependency_overrides: + at_chops: + path: ../at_chops + at_lookup: + path: ../at_lookup + + dev_dependencies: lints: ^2.1.0 test: ^1.24.2 From 964a1669b26e50a866318bc53f56cd35965a4be7 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Thu, 17 Aug 2023 21:07:16 +0530 Subject: [PATCH 02/35] feat: apkam enroll in onboarding cli --- packages/at_lookup/lib/src/at_lookup.dart | 5 + .../at_lookup/lib/src/at_lookup_impl.dart | 5 +- .../example/apkam_authenticate.dart | 23 ++ .../example/apkam_enroll.dart | 21 ++ .../at_onboarding_cli/example/onboard.dart | 19 ++ .../src/onboard/at_onboarding_service.dart | 13 +- .../onboard/at_onboarding_service_impl.dart | 260 ++++++++++++++---- packages/at_onboarding_cli/pubspec.yaml | 2 + 8 files changed, 289 insertions(+), 59 deletions(-) create mode 100644 packages/at_onboarding_cli/example/apkam_authenticate.dart create mode 100644 packages/at_onboarding_cli/example/apkam_enroll.dart create mode 100644 packages/at_onboarding_cli/example/onboard.dart diff --git a/packages/at_lookup/lib/src/at_lookup.dart b/packages/at_lookup/lib/src/at_lookup.dart index 425c7c95..2d84bc62 100644 --- a/packages/at_lookup/lib/src/at_lookup.dart +++ b/packages/at_lookup/lib/src/at_lookup.dart @@ -49,4 +49,9 @@ abstract class AtLookUp { set hashingAlgoType(HashingAlgoType hashingAlgoType); HashingAlgoType get hashingAlgoType; + + /// EnrollmentId has to be set for clients that are enrolled through APKAM. + set enrollmentId(String? enrollmentId); + + String? get enrollmentId; } diff --git a/packages/at_lookup/lib/src/at_lookup_impl.dart b/packages/at_lookup/lib/src/at_lookup_impl.dart index ae4d5570..2496a42c 100644 --- a/packages/at_lookup/lib/src/at_lookup_impl.dart +++ b/packages/at_lookup/lib/src/at_lookup_impl.dart @@ -557,7 +557,7 @@ class AtLookupImpl implements AtLookUp { if (auth && _isAuthRequired()) { if (_atChops != null) { logger.finer('calling pkam using atchops'); - await pkamAuthenticate(); + await pkamAuthenticate(enrollmentId: enrollmentId); } else if (privateKey != null) { logger.finer('calling pkam without atchops'); await authenticate(privateKey); @@ -634,6 +634,9 @@ class AtLookupImpl implements AtLookUp { @override SigningAlgoType signingAlgoType = SigningAlgoType.rsa2048; + + @override + String? enrollmentId; } class AtLookupSecureSocketFactory { diff --git a/packages/at_onboarding_cli/example/apkam_authenticate.dart b/packages/at_onboarding_cli/example/apkam_authenticate.dart new file mode 100644 index 00000000..7238f825 --- /dev/null +++ b/packages/at_onboarding_cli/example/apkam_authenticate.dart @@ -0,0 +1,23 @@ +import 'package:at_client/at_client.dart'; +import 'package:at_lookup/at_lookup.dart'; +import 'package:at_onboarding_cli/at_onboarding_cli.dart'; + +Future main() async { + final enrollIdFromServer = '867307c7-53bd-4736-8fe7-1520de58ce78'; + final atSign = '@alice'; + AtOnboardingPreference atOnboardingPreference = AtOnboardingPreference() + ..namespace = + 'buzz' // unique identifier that can be used to identify data from your app + ..atKeysFilePath = '/home/user/atsign/alice_buzzkey.atKeys' + ..rootDomain = 'vip.ve.atsign.zone' + ..enrollmentId = enrollIdFromServer; + AtOnboardingService? onboardingService = + AtOnboardingServiceImpl(atSign, atOnboardingPreference); + await onboardingService.authenticate( + enrollmentId: enrollIdFromServer); // when authenticating + AtLookUp? atLookup = onboardingService.atLookUp; + AtClient? client = onboardingService.atClient; + print(await client?.getKeys()); + print(await atLookup?.scan(regex: 'publickey')); + await onboardingService.close(); +} diff --git a/packages/at_onboarding_cli/example/apkam_enroll.dart b/packages/at_onboarding_cli/example/apkam_enroll.dart new file mode 100644 index 00000000..0249929e --- /dev/null +++ b/packages/at_onboarding_cli/example/apkam_enroll.dart @@ -0,0 +1,21 @@ +import 'package:at_client/at_client.dart'; +import 'package:at_lookup/at_lookup.dart'; +import 'package:at_onboarding_cli/at_onboarding_cli.dart'; +import 'package:at_utils/at_logger.dart'; + +Future main() async { + AtSignLogger.root_level = 'finer'; + final atSign = '@alice'; + AtOnboardingPreference atOnboardingPreference = AtOnboardingPreference() + ..namespace = + 'buzz' // unique identifier that can be used to identify data from your app + ..atKeysFilePath = '/home/user/atsign/alice_buzzkey.atKeys' + ..appName = 'buzz' + ..deviceName = 'iphone' + ..rootDomain = 'vip.ve.atsign.zone'; + AtOnboardingService? onboardingService = + AtOnboardingServiceImpl(atSign, atOnboardingPreference); + Map namespaces = {"buzz": "rw"}; + // run totp:get from enrolled client and pass the otp + await onboardingService.enroll('buzz', 'iphone', "562137", namespaces); +} diff --git a/packages/at_onboarding_cli/example/onboard.dart b/packages/at_onboarding_cli/example/onboard.dart new file mode 100644 index 00000000..3c5fe939 --- /dev/null +++ b/packages/at_onboarding_cli/example/onboard.dart @@ -0,0 +1,19 @@ +import 'package:at_onboarding_cli/at_onboarding_cli.dart'; +import 'package:at_utils/at_logger.dart'; + +Future main() async { + AtSignLogger.root_level = 'info'; + final atSign = '@alice'; + 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' + ..appName = 'wavi' + ..deviceName = 'pixel' + ..rootDomain = 'vip.ve.atsign.zone'; + AtOnboardingService? onboardingService = + AtOnboardingServiceImpl(atSign, atOnboardingPreference); + await onboardingService.onboard(); +} diff --git a/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart b/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart index 7049c6e9..45b6e0de 100644 --- a/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart +++ b/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart @@ -7,9 +7,18 @@ abstract class AtOnboardingService { ///returns true if onboarded Future 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 authenticate(); + Future 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 + /// totp - otp retrieved from an already enrolled app + /// namespaces - key-value pair of namespace-access of the requesting client e.g {"wavi":"rw","contacts":"r"} + Future enroll(String appName, String deviceName, String totp, + Map namespaces); ///returns an authenticated instance of AtClient @Deprecated('use getter') 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 f7d00044..37e941c1 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 @@ -32,6 +32,8 @@ class AtOnboardingServiceImpl implements AtOnboardingService { AtOnboardingPreference atOnboardingPreference; AtClient? _atClient; AtLookUp? _atLookUp; + //#TODO move to config/preference + static const int _apkamAuthRetryDurationMins = 30; /// The object which controls what types of AtClients, NotificationServices /// and SyncServices get created when we call [AtClientManager.setCurrentAtSign]. @@ -64,6 +66,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { atChops: atChops, serviceFactory: atServiceFactory); // ??= to support mocking _atLookUp ??= atClientManager.atClient.getRemoteSecondary()?.atLookUp; + _atLookUp?.enrollmentId = atOnboardingPreference.enrollmentId; _atLookUp?.signingAlgoType = atOnboardingPreference.signingAlgoType; _atLookUp?.hashingAlgoType = atOnboardingPreference.hashingAlgoType; _atClient ??= atClientManager.atClient; @@ -142,6 +145,114 @@ class AtOnboardingServiceImpl implements AtOnboardingService { return _isAtsignOnboarded; } + @override + Future enroll(String appName, String deviceName, String totp, + Map namespaces) async { + if (appName == null || deviceName == null) { + throw AtEnrollmentException( + 'appName and deviceName are mandatory for enrollment'); + } + final Duration retryInterval = + Duration(minutes: _apkamAuthRetryDurationMins); + logger.info('Generating apkam encryption keypair'); + //1. Generate new apkam key pair and apkam symmetric key + var apkamKeyPair = generateRsaKeypair(); + var apkamSymmetricKey = generateAESKey(); + + AtLookupImpl atLookUpImpl = AtLookupImpl(_atSign, + atOnboardingPreference.rootDomain, atOnboardingPreference.rootPort); + //2. Retrieve default encryption public key and encrypt apkam symmetric key + var defaultEncryptionPublicKey = + await _retrieveEncryptionPublicKey(atLookUpImpl); + var encryptedApkamSymmetricKey = EncryptionUtil.encryptKey( + apkamSymmetricKey, defaultEncryptionPublicKey); + + //3. Send enroll request to server + var enrollmentIdFromServer = await _sendEnrollRequest( + appName, + deviceName, + totp, + namespaces, + apkamKeyPair.publicKey.toString(), + encryptedApkamSymmetricKey, + atLookUpImpl); + + //4. Create at chops instance + var atChopsKeys = AtChopsKeys.create( + null, + AtPkamKeyPair.create(apkamKeyPair.publicKey.toString(), + apkamKeyPair.privateKey.toString())); + atLookUpImpl.atChops = AtChopsImpl(atChopsKeys); + //5. try pkam auth every 30 mins or configured time interval until enrollment timesout or approved/denied by another authorized app + var pkamAuthResult = false; + while (!pkamAuthResult) { + pkamAuthResult = await _attemptPkamAuth( + atLookUpImpl, enrollmentIdFromServer, retryInterval); + } + if (!pkamAuthResult) { + throw AtEnrollmentException( + 'Pkam auth with enrollmentId: $enrollmentIdFromServer failed after multiple retries'); + } + + //6. Retrieve encrypted keys from server + var privateKeyCommand = + 'keys:get:keyName:$enrollmentIdFromServer.$defaultEncryptionPrivateKey.__manage$_atSign\n'; + var selfEncryptionKeyCommand = + 'keys:get:keyName:$enrollmentIdFromServer.$defaultSelfEncryptionKey.__manage$_atSign\n'; + var getPrivateKeyResult; + var getSelfEncryptionKeyResult; + try { + getPrivateKeyResult = + await atLookUpImpl.executeCommand(privateKeyCommand, auth: true); + getSelfEncryptionKeyResult = await atLookUpImpl + .executeCommand(selfEncryptionKeyCommand, auth: true); + } on Exception catch (e) { + throw AtEnrollmentException( + 'Exception while getting encrypted private key/self key from server: $e'); + } + getPrivateKeyResult = getPrivateKeyResult.replaceFirst('data:', ''); + var privateKeyResultJson = jsonDecode(getPrivateKeyResult); + getSelfEncryptionKeyResult = + getSelfEncryptionKeyResult.replaceFirst('data:', ''); + var selfEncryptionKeyResultJson = jsonDecode(getSelfEncryptionKeyResult); + var decryptedEncryptionPrivateKey = EncryptionUtil.decryptValue( + privateKeyResultJson['value'], apkamSymmetricKey); + var decryptedSelfEncryptionKey = EncryptionUtil.decryptValue( + selfEncryptionKeyResultJson['value'], apkamSymmetricKey); + + var atSecurityKeys = AtSecurityKeys() + ..defaultEncryptionPrivateKey = decryptedEncryptionPrivateKey + ..defaultEncryptionPublicKey = defaultEncryptionPublicKey + ..apkamSymmetricKey = apkamSymmetricKey + ..defaultSelfEncryptionKey = decryptedSelfEncryptionKey + ..apkamPublicKey = apkamKeyPair.publicKey.toString() + ..apkamPrivateKey = apkamKeyPair.privateKey.toString(); + await _generateAtKeysFile(enrollmentIdFromServer, atSecurityKeys); + return pkamAuthResult; + } + + Future _attemptPkamAuth(AtLookUp atLookUp, + String enrollmentIdFromServer, Duration retryInterval) async { + try { + var pkamResult = + await atLookUp.pkamAuthenticate(enrollmentId: enrollmentIdFromServer); + if (pkamResult) { + return true; + } + } on UnAuthenticatedException catch (e) { + if (e.message.contains('error:AT0401')) { + logger.finer('Retrying pkam auth'); + await Future.delayed(retryInterval); + } else if (e.message.contains('error:AT0402')) { + //# TODO change the error code once server bug is fixed + logger.finer( + 'enrollmentId $enrollmentIdFromServer denied.Exiting pkam retry logic'); + throw AtEnrollmentException('enrollment denied'); + } + } + return false; + } + ///method to generate/update encryption key-pairs to activate an atsign Future _activateAtsign(AtLookupImpl atLookUpImpl) async { //1. Generate pkam key pair(if authMode is keyFile), encryption key pair, self encryption key and apkam symmetric key pair @@ -159,7 +270,6 @@ class AtOnboardingServiceImpl implements AtOnboardingService { EncryptionUtil.encryptValue(atSecurityKeys.defaultSelfEncryptionKey!, atSecurityKeys.apkamSymmetricKey!); enrollBuilder.apkamPublicKey = atSecurityKeys.apkamPublicKey; - print(enrollBuilder.buildCommand()); //2. Send enroll request to server var enrollResult = await atLookUpImpl @@ -170,7 +280,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { throw AtOnboardingException('Enrollment error:$enrollResult'); } enrollResult = enrollResult.replaceFirst('data:', ''); - print('***enrollResult: $enrollResult'); + logger.finer('enrollResult: $enrollResult'); var enrollResultJson = jsonDecode(enrollResult); var enrollmentIdFromServer = enrollResultJson[enrollmentId]; var enrollmentStatus = enrollResultJson['status']; @@ -202,11 +312,6 @@ class AtOnboardingServiceImpl implements AtOnboardingService { throw AtOnboardingException( 'Pkam auth with enrollmentId-$enrollmentIdFromServer failed'); } - print('*** _isPkamAuthenticated:$_isPkamAuthenticated'); - //2. generate .atKeys file using a copy of atKeysMap - - // _isPkamAuthenticated = (await _atLookUp?.pkamAuthenticate())!; - //5. If Pkam auth is success, update encryption public key to secondary and delete cram key from server if (_isPkamAuthenticated) { final encryptionPublicKey = atSecurityKeys.defaultEncryptionPublicKey; @@ -232,6 +337,36 @@ class AtOnboardingServiceImpl implements AtOnboardingService { } } + Future _sendEnrollRequest( + String appName, + String deviceName, + String totp, + Map namespaces, + String apkamPublicKey, + String encryptedApkamSymmetricKey, + AtLookupImpl atLookUpImpl) async { + var enrollVerbBuilder = EnrollVerbBuilder() + ..appName = appName + ..deviceName = deviceName + ..namespaces = namespaces + ..totp = totp + ..apkamPublicKey = apkamPublicKey + ..encryptedAPKAMSymmetricKey = encryptedApkamSymmetricKey; + var enrollResult = + await atLookUpImpl.executeCommand(enrollVerbBuilder.buildCommand()); + if (enrollResult == null || + enrollResult.isEmpty || + enrollResult.startsWith('error:')) { + throw AtEnrollmentException( + 'Enrollment response from server: $enrollResult'); + } + enrollResult = enrollResult.replaceFirst('data:', ''); + var enrollJson = jsonDecode(enrollResult); + var enrollmentIdFromServer = enrollJson[enrollmentId]; + logger.finer('enrollmentIdFromServer: $enrollmentIdFromServer'); + return enrollmentIdFromServer; + } + AtSecurityKeys _generateKeyPairs() { // generate user encryption keypair logger.info('Generating encryption keypair'); @@ -250,7 +385,6 @@ class AtOnboardingServiceImpl implements AtOnboardingService { var apkamRsaKeypair = generateRsaKeypair(); atKeysFile.apkamPublicKey = apkamRsaKeypair.publicKey.toString(); atKeysFile.apkamPrivateKey = apkamRsaKeypair.privateKey.toString(); - print('apkamPrivateKey:${atKeysFile.apkamPrivateKey}'); apkamPublicKey = apkamRsaKeypair.publicKey.toString(); } else if (atOnboardingPreference.authMode == PkamAuthMode.sim) { // get the public key from secure element @@ -284,28 +418,30 @@ class AtOnboardingServiceImpl implements AtOnboardingService { ///write newly created encryption keypairs into atKeys file Future _generateAtKeysFile( String currentEnrollmentId, AtSecurityKeys atSecurityKeys) async { - Map atKeysMap = {}; - //encrypting all keys with self encryption key + final atKeysMap = { + AuthKeyType.pkamPublicKey: EncryptionUtil.encryptValue( + atSecurityKeys.apkamPublicKey!, + atSecurityKeys.defaultSelfEncryptionKey!, + ), + AuthKeyType.encryptionPublicKey: EncryptionUtil.encryptValue( + atSecurityKeys.defaultEncryptionPublicKey!, + atSecurityKeys.defaultSelfEncryptionKey!, + ), + AuthKeyType.encryptionPrivateKey: EncryptionUtil.encryptValue( + atSecurityKeys.defaultEncryptionPrivateKey!, + atSecurityKeys.defaultSelfEncryptionKey!, + ), + AuthKeyType.selfEncryptionKey: atSecurityKeys.defaultSelfEncryptionKey!, + _atSign: atSecurityKeys.defaultSelfEncryptionKey!, + AuthKeyType.apkamSymmetricKey: atSecurityKeys.apkamSymmetricKey!, + 'enrollmentId': currentEnrollmentId, + }; + if (atOnboardingPreference.authMode == PkamAuthMode.keysFile) { atKeysMap[AuthKeyType.pkamPrivateKey] = EncryptionUtil.encryptValue( atSecurityKeys.apkamPrivateKey!, atSecurityKeys.defaultSelfEncryptionKey!); } - atKeysMap[AuthKeyType.pkamPublicKey] = EncryptionUtil.encryptValue( - atSecurityKeys.apkamPublicKey!, - atSecurityKeys.defaultSelfEncryptionKey!); - atKeysMap[AuthKeyType.encryptionPublicKey] = EncryptionUtil.encryptValue( - atSecurityKeys.defaultEncryptionPublicKey!, - atSecurityKeys.defaultSelfEncryptionKey!); - atKeysMap[AuthKeyType.encryptionPrivateKey] = EncryptionUtil.encryptValue( - atSecurityKeys.defaultEncryptionPrivateKey!, - atSecurityKeys.defaultSelfEncryptionKey!); - atKeysMap[AuthKeyType.selfEncryptionKey] = - atSecurityKeys.defaultSelfEncryptionKey!; - atKeysMap[_atSign] = atSecurityKeys.defaultSelfEncryptionKey!; - atKeysMap[AuthKeyType.apkamSymmetricKey] = - atSecurityKeys.apkamSymmetricKey!; - atKeysMap['enrollmentId'] = currentEnrollmentId; if (!atOnboardingPreference.atKeysFilePath!.endsWith('.atKeys')) { atOnboardingPreference.atKeysFilePath = @@ -330,43 +466,43 @@ class AtOnboardingServiceImpl implements AtOnboardingService { ///back-up encryption keys to local secondary Future _persistKeysLocalSecondary() async { //when authenticating keys need to be fetched from atKeys file - Map atKeysMap = await _decryptAtKeysFile( + AtSecurityKeys atSecurityKeys = _decryptAtKeysFile( (await _readAtKeysFile(atOnboardingPreference.atKeysFilePath))); //backup keys into local secondary bool? response = await _atClient ?.getLocalSecondary() - ?.putValue(AT_PKAM_PUBLIC_KEY, atKeysMap[AuthKeyType.pkamPublicKey]!); + ?.putValue(AT_PKAM_PUBLIC_KEY, atSecurityKeys.apkamPublicKey!); logger.finer('PkamPublicKey persist to localSecondary: status $response'); // save pkam private key only when auth mode is keyFile. if auth mode is sim/any other secure element private key cannot be read and hence will not be part of keys file if (atOnboardingPreference.authMode == PkamAuthMode.keysFile) { - response = await _atClient?.getLocalSecondary()?.putValue( - AT_PKAM_PRIVATE_KEY, atKeysMap[AuthKeyType.pkamPrivateKey]!); + response = await _atClient + ?.getLocalSecondary() + ?.putValue(AT_PKAM_PRIVATE_KEY, atSecurityKeys.apkamPrivateKey!); logger .finer('PkamPrivateKey persist to localSecondary: status $response'); } response = await _atClient?.getLocalSecondary()?.putValue( '$AT_ENCRYPTION_PUBLIC_KEY$_atSign', - atKeysMap[AuthKeyType.encryptionPublicKey]!); + atSecurityKeys.defaultEncryptionPublicKey!); logger.finer( 'EncryptionPublicKey persist to localSecondary: status $response'); response = await _atClient?.getLocalSecondary()?.putValue( - AT_ENCRYPTION_PRIVATE_KEY, - atKeysMap[AuthKeyType.encryptionPrivateKey]!); + AT_ENCRYPTION_PRIVATE_KEY, atSecurityKeys.defaultEncryptionPrivateKey!); logger.finer( 'EncryptionPrivateKey persist to localSecondary: status $response'); response = await _atClient?.getLocalSecondary()?.putValue( - AT_ENCRYPTION_SELF_KEY, atKeysMap[AuthKeyType.selfEncryptionKey]!); + AT_ENCRYPTION_SELF_KEY, atSecurityKeys.defaultSelfEncryptionKey!); logger .finer('SelfEncryptionKey persist to localSecondary: status $response'); } @override - Future authenticate() async { + Future authenticate({String? enrollmentId}) async { // decrypts all the keys in .atKeysFile using the SelfEncryptionKey // and stores the keys in a map - var atKeysFileDataMap = await _decryptAtKeysFile( + var atSecurityKeys = await _decryptAtKeysFile( await _readAtKeysFile(atOnboardingPreference.atKeysFilePath)); - var pkamPrivateKey = atKeysFileDataMap[AuthKeyType.pkamPrivateKey]; + var pkamPrivateKey = atSecurityKeys.apkamPrivateKey; if (atOnboardingPreference.authMode == PkamAuthMode.keysFile && pkamPrivateKey == null) { @@ -375,20 +511,21 @@ class AtOnboardingServiceImpl implements AtOnboardingService { '${atOnboardingPreference.atKeysFilePath}. Please provide a valid atKeys file', exceptionScenario: ExceptionScenario.invalidValueProvided); } - // #TODO replace with AtKeysFile - // await _init(atKeysFileDataMap); + await _init(atSecurityKeys); logger.finer('Authenticating using PKAM'); try { - _isPkamAuthenticated = (await _atLookUp?.pkamAuthenticate())!; + _isPkamAuthenticated = + (await _atLookUp?.pkamAuthenticate(enrollmentId: enrollmentId))!; } on Exception catch (e) { logger.severe('Caught exception: $e'); - throw UnAuthenticatedException('Unable to authenticate.' - ' Please provide a valid keys file'); + throw UnAuthenticatedException('Unable to authenticate'); } logger.finer( 'PKAM auth result: ${_isPkamAuthenticated ? 'success' : 'failed'}'); - if (!_isAtsignOnboarded && atOnboardingPreference.atKeysFilePath != null) { + if (!_isAtsignOnboarded && + _isPkamAuthenticated && + atOnboardingPreference.atKeysFilePath != null) { await _persistKeysLocalSecondary(); } @@ -429,27 +566,38 @@ class AtOnboardingServiceImpl implements AtOnboardingService { return jsonData![AuthKeyType.selfEncryptionKey]!; } - ///decrypt keys using self_encryption_key - ///returns map containing decrypted atKeys - Future> _decryptAtKeysFile( - Map jsonData) async { + AtSecurityKeys _decryptAtKeysFile(Map jsonData) { + var securityKeys = AtSecurityKeys(); String decryptionKey = _getDecryptionKey(jsonData); - Map atKeysMap = { - AuthKeyType.encryptionPublicKey: EncryptionUtil.decryptValue( - jsonData[AuthKeyType.encryptionPublicKey]!, decryptionKey), - AuthKeyType.encryptionPrivateKey: EncryptionUtil.decryptValue( - jsonData[AuthKeyType.encryptionPrivateKey]!, decryptionKey), - AuthKeyType.selfEncryptionKey: decryptionKey, - }; - atKeysMap[AuthKeyType.pkamPublicKey] = EncryptionUtil.decryptValue( + securityKeys.defaultEncryptionPublicKey = EncryptionUtil.decryptValue( + jsonData[AuthKeyType.encryptionPublicKey]!, decryptionKey); + securityKeys.defaultEncryptionPrivateKey = EncryptionUtil.decryptValue( + jsonData[AuthKeyType.encryptionPrivateKey]!, decryptionKey); + securityKeys.defaultSelfEncryptionKey = decryptionKey; + securityKeys.apkamPublicKey = EncryptionUtil.decryptValue( jsonData[AuthKeyType.pkamPublicKey]!, decryptionKey); // pkam private key will not be saved in keyfile if auth mode is sim/any other secure element. // decrypt the private key only when auth mode is keysFile if (atOnboardingPreference.authMode == PkamAuthMode.keysFile) { - atKeysMap[AuthKeyType.pkamPrivateKey] = EncryptionUtil.decryptValue( + securityKeys.apkamPrivateKey = EncryptionUtil.decryptValue( jsonData[AuthKeyType.pkamPrivateKey]!, decryptionKey); } - return atKeysMap; + securityKeys.apkamSymmetricKey = jsonData[AuthKeyType.apkamSymmetricKey]; + securityKeys.enrollmentId = jsonData[enrollmentId]; + return securityKeys; + } + + Future _retrieveEncryptionPublicKey(AtLookUp atLookupImpl) async { + var lookupVerbBuilder = LookupVerbBuilder() + ..atKey = 'publickey' + ..sharedBy = _atSign; + var lookupResult = await atLookupImpl.executeVerb(lookupVerbBuilder); + if (lookupResult == null || lookupResult.isEmpty) { + throw AtEnrollmentException( + 'Unable to lookup encryption public key. Server response is null/empty'); + } + var defaultEncryptionPublicKey = lookupResult.replaceFirst('data:', ''); + return defaultEncryptionPublicKey; } ///generates random RSA keypair diff --git a/packages/at_onboarding_cli/pubspec.yaml b/packages/at_onboarding_cli/pubspec.yaml index 5258b85a..0372c92c 100644 --- a/packages/at_onboarding_cli/pubspec.yaml +++ b/packages/at_onboarding_cli/pubspec.yaml @@ -32,6 +32,8 @@ dependency_overrides: path: ../at_chops at_lookup: path: ../at_lookup + at_client: + path: ../../../at_client_sdk/packages/at_client dev_dependencies: From b5c6cef27be2e16cb3405d743b34249c390514d8 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Thu, 17 Aug 2023 22:34:26 +0530 Subject: [PATCH 03/35] fix: code refactoring --- packages/at_lookup/lib/src/at_lookup.dart | 2 + .../at_lookup/lib/src/at_lookup_impl.dart | 1 + .../onboard/at_onboarding_service_impl.dart | 80 +++++++++++++------ .../src/util/at_onboarding_preference.dart | 2 + 4 files changed, 60 insertions(+), 25 deletions(-) diff --git a/packages/at_lookup/lib/src/at_lookup.dart b/packages/at_lookup/lib/src/at_lookup.dart index 2d84bc62..f867e221 100644 --- a/packages/at_lookup/lib/src/at_lookup.dart +++ b/packages/at_lookup/lib/src/at_lookup.dart @@ -26,6 +26,8 @@ abstract class AtLookUp { Future executeVerb(VerbBuilder builder, {bool sync = false}); + Future executeCommand(String command, {bool auth = false}); + /// performs a PKAM authentication using private key on the client side and public key on secondary server /// Default signing algorithm for pkam signature is [SigningAlgoType.rsa2048] and default hashing algorithm is [HashingAlgoType.sha256] /// Optionally pass enrollmentId is the client is enrolled using APKAM diff --git a/packages/at_lookup/lib/src/at_lookup_impl.dart b/packages/at_lookup/lib/src/at_lookup_impl.dart index 2496a42c..71b6c3c6 100644 --- a/packages/at_lookup/lib/src/at_lookup_impl.dart +++ b/packages/at_lookup/lib/src/at_lookup_impl.dart @@ -383,6 +383,7 @@ class AtLookupImpl implements AtLookUp { return await _process(atCommand, auth: true); } + @override Future executeCommand(String atCommand, {bool auth = false}) async { return await _process(atCommand, auth: auth); } 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 37e941c1..a5d2bda7 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 @@ -32,8 +32,6 @@ class AtOnboardingServiceImpl implements AtOnboardingService { AtOnboardingPreference atOnboardingPreference; AtClient? _atClient; AtLookUp? _atLookUp; - //#TODO move to config/preference - static const int _apkamAuthRetryDurationMins = 30; /// The object which controls what types of AtClients, NotificationServices /// and SyncServices get created when we call [AtClientManager.setCurrentAtSign]. @@ -153,7 +151,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { 'appName and deviceName are mandatory for enrollment'); } final Duration retryInterval = - Duration(minutes: _apkamAuthRetryDurationMins); + Duration(minutes: atOnboardingPreference.apkamAuthRetryDurationMins); logger.info('Generating apkam encryption keypair'); //1. Generate new apkam key pair and apkam symmetric key var apkamKeyPair = generateRsaKeypair(); @@ -161,6 +159,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { AtLookupImpl atLookUpImpl = AtLookupImpl(_atSign, atOnboardingPreference.rootDomain, atOnboardingPreference.rootPort); + //2. Retrieve default encryption public key and encrypt apkam symmetric key var defaultEncryptionPublicKey = await _retrieveEncryptionPublicKey(atLookUpImpl); @@ -183,6 +182,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { AtPkamKeyPair.create(apkamKeyPair.publicKey.toString(), apkamKeyPair.privateKey.toString())); atLookUpImpl.atChops = AtChopsImpl(atChopsKeys); + //5. try pkam auth every 30 mins or configured time interval until enrollment timesout or approved/denied by another authorized app var pkamAuthResult = false; while (!pkamAuthResult) { @@ -195,31 +195,16 @@ class AtOnboardingServiceImpl implements AtOnboardingService { } //6. Retrieve encrypted keys from server - var privateKeyCommand = - 'keys:get:keyName:$enrollmentIdFromServer.$defaultEncryptionPrivateKey.__manage$_atSign\n'; - var selfEncryptionKeyCommand = - 'keys:get:keyName:$enrollmentIdFromServer.$defaultSelfEncryptionKey.__manage$_atSign\n'; - var getPrivateKeyResult; - var getSelfEncryptionKeyResult; - try { - getPrivateKeyResult = - await atLookUpImpl.executeCommand(privateKeyCommand, auth: true); - getSelfEncryptionKeyResult = await atLookUpImpl - .executeCommand(selfEncryptionKeyCommand, auth: true); - } on Exception catch (e) { - throw AtEnrollmentException( - 'Exception while getting encrypted private key/self key from server: $e'); - } - getPrivateKeyResult = getPrivateKeyResult.replaceFirst('data:', ''); - var privateKeyResultJson = jsonDecode(getPrivateKeyResult); - getSelfEncryptionKeyResult = - getSelfEncryptionKeyResult.replaceFirst('data:', ''); - var selfEncryptionKeyResultJson = jsonDecode(getSelfEncryptionKeyResult); var decryptedEncryptionPrivateKey = EncryptionUtil.decryptValue( - privateKeyResultJson['value'], apkamSymmetricKey); + await _getEncryptionPrivateKeyFromServer( + enrollmentIdFromServer, atLookUpImpl), + apkamSymmetricKey); var decryptedSelfEncryptionKey = EncryptionUtil.decryptValue( - selfEncryptionKeyResultJson['value'], apkamSymmetricKey); + await _getSelfEncryptionKeyFromServer( + enrollmentIdFromServer, atLookUpImpl), + apkamSymmetricKey); + //7. Save security keys and enrollmentId in atKeys file var atSecurityKeys = AtSecurityKeys() ..defaultEncryptionPrivateKey = decryptedEncryptionPrivateKey ..defaultEncryptionPublicKey = defaultEncryptionPublicKey @@ -231,6 +216,51 @@ class AtOnboardingServiceImpl implements AtOnboardingService { return pkamAuthResult; } + Future _getEncryptionPrivateKeyFromServer( + String enrollmentIdFromServer, AtLookUp atLookUp) async { + var privateKeyCommand = + 'keys:get:keyName:$enrollmentIdFromServer.$defaultEncryptionPrivateKey.__manage$_atSign\n'; + var encryptionPrivateKeyFromServer; + try { + var getPrivateKeyResult = + await atLookUp.executeCommand(privateKeyCommand, auth: true); + if (getPrivateKeyResult == null || getPrivateKeyResult.isEmpty) { + throw AtEnrollmentException('$privateKeyCommand returned null/empty'); + } + getPrivateKeyResult = getPrivateKeyResult.replaceFirst('data:', ''); + var privateKeyResultJson = jsonDecode(getPrivateKeyResult); + encryptionPrivateKeyFromServer = privateKeyResultJson['value']; + } on Exception catch (e) { + throw AtEnrollmentException( + 'Exception while getting encrypted private key/self key from server: $e'); + } + return encryptionPrivateKeyFromServer; + } + + Future _getSelfEncryptionKeyFromServer( + String enrollmentIdFromServer, AtLookUp atLookUp) async { + var selfEncryptionKeyCommand = + 'keys:get:keyName:$enrollmentIdFromServer.$defaultSelfEncryptionKey.__manage$_atSign\n'; + var selfEncryptionKeyFromServer; + try { + var getSelfEncryptionKeyResult = + await atLookUp.executeCommand(selfEncryptionKeyCommand, auth: true); + if (getSelfEncryptionKeyResult == null || + getSelfEncryptionKeyResult.isEmpty) { + throw AtEnrollmentException( + '$selfEncryptionKeyCommand returned null/empty'); + } + getSelfEncryptionKeyResult = + getSelfEncryptionKeyResult.replaceFirst('data:', ''); + var selfEncryptionKeyResultJson = jsonDecode(getSelfEncryptionKeyResult); + selfEncryptionKeyFromServer = selfEncryptionKeyResultJson['value']; + } on Exception catch (e) { + throw AtEnrollmentException( + 'Exception while getting encrypted private key/self key from server: $e'); + } + return selfEncryptionKeyFromServer; + } + Future _attemptPkamAuth(AtLookUp atLookUp, String enrollmentIdFromServer, Duration retryInterval) async { try { diff --git a/packages/at_onboarding_cli/lib/src/util/at_onboarding_preference.dart b/packages/at_onboarding_cli/lib/src/util/at_onboarding_preference.dart index 72438bc9..3d7d9358 100644 --- a/packages/at_onboarding_cli/lib/src/util/at_onboarding_preference.dart +++ b/packages/at_onboarding_cli/lib/src/util/at_onboarding_preference.dart @@ -31,4 +31,6 @@ class AtOnboardingPreference extends AtClientPreference { late String appName; late String deviceName; + + int apkamAuthRetryDurationMins = 30; } From a8885d1b3bc7bcd8de5e07ae070daacc971ed6de Mon Sep 17 00:00:00 2001 From: murali-shris Date: Sun, 20 Aug 2023 16:57:26 +0530 Subject: [PATCH 04/35] feat: add enrollresponse and status to at_commons --- packages/at_commons/lib/at_commons.dart | 1 + .../at_commons/lib/src/enroll/enrollment.dart | 27 +++++++++++++++++++ .../example/apkam_enroll.dart | 3 ++- .../src/onboard/at_onboarding_service.dart | 2 +- .../onboard/at_onboarding_service_impl.dart | 24 ++++++++++------- packages/at_onboarding_cli/pubspec.yaml | 2 ++ 6 files changed, 47 insertions(+), 12 deletions(-) create mode 100644 packages/at_commons/lib/src/enroll/enrollment.dart diff --git a/packages/at_commons/lib/at_commons.dart b/packages/at_commons/lib/at_commons.dart index 3fa05e75..906283f0 100644 --- a/packages/at_commons/lib/at_commons.dart +++ b/packages/at_commons/lib/at_commons.dart @@ -36,5 +36,6 @@ export 'package:at_commons/src/verb/update_json.dart'; export 'package:at_commons/src/verb/verb_util.dart'; export 'package:at_commons/src/auth/auth_mode.dart'; export 'package:at_commons/src/verb/enroll_params.dart'; +export 'package:at_commons/src/enroll/enrollment.dart'; @experimental export 'package:at_commons/src/telemetry/at_telemetry.dart'; diff --git a/packages/at_commons/lib/src/enroll/enrollment.dart b/packages/at_commons/lib/src/enroll/enrollment.dart new file mode 100644 index 00000000..04a94acb --- /dev/null +++ b/packages/at_commons/lib/src/enroll/enrollment.dart @@ -0,0 +1,27 @@ +class EnrollResponse { + String enrollmentId; + EnrollStatus enrollStatus; + EnrollResponse(this.enrollmentId, this.enrollStatus); + + @override + String toString() { + return 'EnrollResponse{enrollmentId: $enrollmentId, enrollStatus: $enrollStatus}'; + } +} + +enum EnrollStatus { pending, approved, denied, revoked } + +EnrollStatus getEnrollStatusFromString(String value) { + switch (value) { + case 'approved': + return EnrollStatus.approved; + case 'denied': + return EnrollStatus.denied; + case 'pending': + return EnrollStatus.pending; + case 'revoked': + return EnrollStatus.revoked; + default: + throw ArgumentError('Unknown enroll status string: $value'); + } +} diff --git a/packages/at_onboarding_cli/example/apkam_enroll.dart b/packages/at_onboarding_cli/example/apkam_enroll.dart index 0249929e..f30cf1d3 100644 --- a/packages/at_onboarding_cli/example/apkam_enroll.dart +++ b/packages/at_onboarding_cli/example/apkam_enroll.dart @@ -17,5 +17,6 @@ Future main() async { AtOnboardingServiceImpl(atSign, atOnboardingPreference); Map namespaces = {"buzz": "rw"}; // run totp:get from enrolled client and pass the otp - await onboardingService.enroll('buzz', 'iphone', "562137", namespaces); + var enrollmentResponse = await onboardingService.enroll('buzz', 'iphone', "068881", namespaces); + print('enrollmentResponse: $enrollmentResponse'); } diff --git a/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart b/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart index 45b6e0de..4d792b1b 100644 --- a/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart +++ b/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart @@ -17,7 +17,7 @@ abstract class AtOnboardingService { /// deviceName - device identifier from the requesting application e.g iphone,any unique ID that identifies the requesting client /// totp - otp retrieved from an already enrolled app /// namespaces - key-value pair of namespace-access of the requesting client e.g {"wavi":"rw","contacts":"r"} - Future enroll(String appName, String deviceName, String totp, + Future enroll(String appName, String deviceName, String totp, Map namespaces); ///returns an authenticated instance of AtClient 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 a5d2bda7..336dcd99 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 @@ -144,7 +144,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { } @override - Future enroll(String appName, String deviceName, String totp, + Future enroll(String appName, String deviceName, String totp, Map namespaces) async { if (appName == null || deviceName == null) { throw AtEnrollmentException( @@ -167,7 +167,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { apkamSymmetricKey, defaultEncryptionPublicKey); //3. Send enroll request to server - var enrollmentIdFromServer = await _sendEnrollRequest( + var enrollmentResponse = await _sendEnrollRequest( appName, deviceName, totp, @@ -187,21 +187,21 @@ class AtOnboardingServiceImpl implements AtOnboardingService { var pkamAuthResult = false; while (!pkamAuthResult) { pkamAuthResult = await _attemptPkamAuth( - atLookUpImpl, enrollmentIdFromServer, retryInterval); + atLookUpImpl, enrollmentResponse.enrollmentId, retryInterval); } if (!pkamAuthResult) { throw AtEnrollmentException( - 'Pkam auth with enrollmentId: $enrollmentIdFromServer failed after multiple retries'); + 'Pkam auth with enrollmentId: ${enrollmentResponse.enrollmentId} failed after multiple retries'); } //6. Retrieve encrypted keys from server var decryptedEncryptionPrivateKey = EncryptionUtil.decryptValue( await _getEncryptionPrivateKeyFromServer( - enrollmentIdFromServer, atLookUpImpl), + enrollmentResponse.enrollmentId, atLookUpImpl), apkamSymmetricKey); var decryptedSelfEncryptionKey = EncryptionUtil.decryptValue( await _getSelfEncryptionKeyFromServer( - enrollmentIdFromServer, atLookUpImpl), + enrollmentResponse.enrollmentId, atLookUpImpl), apkamSymmetricKey); //7. Save security keys and enrollmentId in atKeys file @@ -212,8 +212,11 @@ class AtOnboardingServiceImpl implements AtOnboardingService { ..defaultSelfEncryptionKey = decryptedSelfEncryptionKey ..apkamPublicKey = apkamKeyPair.publicKey.toString() ..apkamPrivateKey = apkamKeyPair.privateKey.toString(); - await _generateAtKeysFile(enrollmentIdFromServer, atSecurityKeys); - return pkamAuthResult; + await _generateAtKeysFile(enrollmentResponse.enrollmentId, atSecurityKeys); + if (pkamAuthResult) { + enrollmentResponse.enrollStatus = EnrollStatus.approved; + } + return enrollmentResponse; } Future _getEncryptionPrivateKeyFromServer( @@ -367,7 +370,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { } } - Future _sendEnrollRequest( + Future _sendEnrollRequest( String appName, String deviceName, String totp, @@ -394,7 +397,8 @@ class AtOnboardingServiceImpl implements AtOnboardingService { var enrollJson = jsonDecode(enrollResult); var enrollmentIdFromServer = enrollJson[enrollmentId]; logger.finer('enrollmentIdFromServer: $enrollmentIdFromServer'); - return enrollmentIdFromServer; + return EnrollResponse(enrollmentIdFromServer, + getEnrollStatusFromString(enrollJson['status'])); } AtSecurityKeys _generateKeyPairs() { diff --git a/packages/at_onboarding_cli/pubspec.yaml b/packages/at_onboarding_cli/pubspec.yaml index 0372c92c..37b83a64 100644 --- a/packages/at_onboarding_cli/pubspec.yaml +++ b/packages/at_onboarding_cli/pubspec.yaml @@ -32,6 +32,8 @@ dependency_overrides: path: ../at_chops at_lookup: path: ../at_lookup + at_commons: + path: ../at_commons at_client: path: ../../../at_client_sdk/packages/at_client From 6acd48782ed85f5d8e34665965c16693905fd202 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Tue, 29 Aug 2023 15:30:20 +0530 Subject: [PATCH 05/35] feat: functional tests and bug fixes for apkam onboarding --- .../example/apkam_authenticate.dart | 8 +- .../src/onboard/at_onboarding_service.dart | 6 +- .../onboard/at_onboarding_service_impl.dart | 166 ++++++++++++----- packages/at_onboarding_cli/pubspec.yaml | 14 +- .../pubspec.yaml | 16 +- .../test/enrollment_test.dart | 175 ++++++++++++++++++ 6 files changed, 319 insertions(+), 66 deletions(-) create mode 100644 tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart diff --git a/packages/at_onboarding_cli/example/apkam_authenticate.dart b/packages/at_onboarding_cli/example/apkam_authenticate.dart index 7238f825..733643da 100644 --- a/packages/at_onboarding_cli/example/apkam_authenticate.dart +++ b/packages/at_onboarding_cli/example/apkam_authenticate.dart @@ -9,10 +9,10 @@ Future main() async { ..namespace = 'buzz' // unique identifier that can be used to identify data from your app ..atKeysFilePath = '/home/user/atsign/alice_buzzkey.atKeys' - ..rootDomain = 'vip.ve.atsign.zone' - ..enrollmentId = enrollIdFromServer; - AtOnboardingService? onboardingService = - AtOnboardingServiceImpl(atSign, atOnboardingPreference); + ..rootDomain = 'vip.ve.atsign.zone'; + AtOnboardingService? onboardingService = AtOnboardingServiceImpl( + atSign, atOnboardingPreference, + enrollmentId: enrollIdFromServer); await onboardingService.authenticate( enrollmentId: enrollIdFromServer); // when authenticating AtLookUp? atLookup = onboardingService.atLookUp; diff --git a/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart b/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart index 4d792b1b..ae2e087b 100644 --- a/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart +++ b/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart @@ -1,6 +1,7 @@ import 'package:at_chops/at_chops.dart'; import 'package:at_client/at_client.dart'; import 'package:at_lookup/at_lookup.dart'; +import 'package:at_onboarding_cli/at_onboarding_cli.dart'; abstract class AtOnboardingService { ///perform initial one_time authentication to activate the atsign @@ -17,8 +18,11 @@ abstract class AtOnboardingService { /// deviceName - device identifier from the requesting application e.g iphone,any unique ID that identifies the requesting client /// totp - 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 enroll(String appName, String deviceName, String totp, - Map namespaces); + Map namespaces, + {int? pkamRetryIntervalMins}); ///returns an authenticated instance of AtClient @Deprecated('use getter') 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 336dcd99..6a8e1625 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 @@ -1,5 +1,6 @@ // ignore_for_file: unnecessary_null_comparison +import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -10,7 +11,6 @@ import 'package:at_onboarding_cli/src/util/at_onboarding_exceptions.dart'; import 'package:at_server_status/at_server_status.dart'; import 'package:at_utils/at_utils.dart'; import 'package:at_commons/at_builders.dart'; -import 'package:at_commons/at_commons.dart'; import 'package:at_onboarding_cli/src/factory/service_factories.dart'; import 'package:at_lookup/at_lookup.dart'; import 'package:at_onboarding_cli/at_onboarding_cli.dart'; @@ -33,6 +33,10 @@ class AtOnboardingServiceImpl implements AtOnboardingService { AtClient? _atClient; AtLookUp? _atLookUp; + final StreamController _pkamSuccessController = + StreamController(); + Stream get _onPkamSuccess => _pkamSuccessController.stream; + /// The object which controls what types of AtClients, NotificationServices /// and SyncServices get created when we call [AtClientManager.setCurrentAtSign]. /// If [atServiceFactory] is not set, AtClientManager.setCurrentAtSign will use @@ -40,7 +44,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { AtServiceFactory? atServiceFactory; AtOnboardingServiceImpl(atsign, this.atOnboardingPreference, - {this.atServiceFactory}) { + {this.atServiceFactory, String? enrollmentId}) { // performs atSign format checks on the atSign _atSign = AtUtils.fixAtSign(atsign); @@ -54,25 +58,27 @@ class AtOnboardingServiceImpl implements AtOnboardingService { HomeDirectoryUtil.getAtKeysPath(_atSign); } - Future _initAtClient(AtChops atChops) async { + Future _initAtClient(AtChops atChops, {String? enrollmentId}) async { AtClientManager atClientManager = AtClientManager.getInstance(); if (atOnboardingPreference.skipSync == true) { atServiceFactory = ServiceFactoryWithNoOpSyncService(); } await atClientManager.setCurrentAtSign( _atSign, atOnboardingPreference.namespace, atOnboardingPreference, - atChops: atChops, serviceFactory: atServiceFactory); + atChops: atChops, + serviceFactory: atServiceFactory, + enrollmentId: enrollmentId); // ??= to support mocking _atLookUp ??= atClientManager.atClient.getRemoteSecondary()?.atLookUp; - _atLookUp?.enrollmentId = atOnboardingPreference.enrollmentId; + _atLookUp?.enrollmentId = enrollmentId; _atLookUp?.signingAlgoType = atOnboardingPreference.signingAlgoType; _atLookUp?.hashingAlgoType = atOnboardingPreference.hashingAlgoType; _atClient ??= atClientManager.atClient; } - Future _init(AtSecurityKeys atKeysFile) async { + Future _init(AtSecurityKeys atKeysFile, {String? enrollmentId}) async { atChops ??= _createAtChops(atKeysFile); - await _initAtClient(atChops!); + await _initAtClient(atChops!, enrollmentId: enrollmentId); _atLookUp!.atChops = atChops; _atClient!.atChops = atChops; _atClient!.getPreferences()!.useAtChops = true; @@ -145,14 +151,15 @@ class AtOnboardingServiceImpl implements AtOnboardingService { @override Future enroll(String appName, String deviceName, String totp, - Map namespaces) async { + Map namespaces, + {int? pkamRetryIntervalMins}) async { if (appName == null || deviceName == null) { throw AtEnrollmentException( 'appName and deviceName are mandatory for enrollment'); } - final Duration retryInterval = - Duration(minutes: atOnboardingPreference.apkamAuthRetryDurationMins); - logger.info('Generating apkam encryption keypair'); + pkamRetryIntervalMins ??= atOnboardingPreference.apkamAuthRetryDurationMins; + final Duration retryInterval = Duration(minutes: pkamRetryIntervalMins); + logger.info('Generating apkam encryption keypair and apkam symmetric key'); //1. Generate new apkam key pair and apkam symmetric key var apkamKeyPair = generateRsaKeypair(); var apkamSymmetricKey = generateAESKey(); @@ -175,6 +182,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { apkamKeyPair.publicKey.toString(), encryptedApkamSymmetricKey, atLookUpImpl); + logger.finer('EnrollmentResponse from server: $enrollmentResponse'); //4. Create at chops instance var atChopsKeys = AtChopsKeys.create( @@ -183,47 +191,82 @@ class AtOnboardingServiceImpl implements AtOnboardingService { apkamKeyPair.privateKey.toString())); atLookUpImpl.atChops = AtChopsImpl(atChopsKeys); - //5. try pkam auth every 30 mins or configured time interval until enrollment timesout or approved/denied by another authorized app - var pkamAuthResult = false; - while (!pkamAuthResult) { - pkamAuthResult = await _attemptPkamAuth( - atLookUpImpl, enrollmentResponse.enrollmentId, retryInterval); - } - if (!pkamAuthResult) { - throw AtEnrollmentException( - 'Pkam auth with enrollmentId: ${enrollmentResponse.enrollmentId} failed after multiple retries'); - } - - //6. Retrieve encrypted keys from server - var decryptedEncryptionPrivateKey = EncryptionUtil.decryptValue( - await _getEncryptionPrivateKeyFromServer( - enrollmentResponse.enrollmentId, atLookUpImpl), - apkamSymmetricKey); - var decryptedSelfEncryptionKey = EncryptionUtil.decryptValue( - await _getSelfEncryptionKeyFromServer( - enrollmentResponse.enrollmentId, atLookUpImpl), - apkamSymmetricKey); - - //7. Save security keys and enrollmentId in atKeys file - var atSecurityKeys = AtSecurityKeys() - ..defaultEncryptionPrivateKey = decryptedEncryptionPrivateKey - ..defaultEncryptionPublicKey = defaultEncryptionPublicKey - ..apkamSymmetricKey = apkamSymmetricKey - ..defaultSelfEncryptionKey = decryptedSelfEncryptionKey - ..apkamPublicKey = apkamKeyPair.publicKey.toString() - ..apkamPrivateKey = apkamKeyPair.privateKey.toString(); - await _generateAtKeysFile(enrollmentResponse.enrollmentId, atSecurityKeys); - if (pkamAuthResult) { - enrollmentResponse.enrollStatus = EnrollStatus.approved; - } + // Pkam auth will be attempted asynchronously until enrollment is approved/denied + _attemptPkamAuthAsync( + atLookUpImpl, + enrollmentResponse.enrollmentId, + retryInterval, + apkamSymmetricKey, + defaultEncryptionPublicKey, + apkamKeyPair); + + // Upon successful pkam auth, callback _listenToPkamSuccessStream will be invoked + _listenToPkamSuccessStream(atLookUpImpl, apkamSymmetricKey, + defaultEncryptionPublicKey, apkamKeyPair); + return enrollmentResponse; } + void _listenToPkamSuccessStream( + AtLookupImpl atLookUpImpl, + String apkamSymmetricKey, + String defaultEncryptionPublicKey, + RSAKeypair apkamKeyPair) { + _onPkamSuccess.listen((enrollmentIdFromServer) async { + logger.finer('_listenToPkamSuccessStream invoked'); + var decryptedEncryptionPrivateKey = EncryptionUtil.decryptValue( + await _getEncryptionPrivateKeyFromServer( + enrollmentIdFromServer, atLookUpImpl), + apkamSymmetricKey); + var decryptedSelfEncryptionKey = EncryptionUtil.decryptValue( + await _getSelfEncryptionKeyFromServer( + enrollmentIdFromServer, atLookUpImpl), + apkamSymmetricKey); + + var atSecurityKeys = AtSecurityKeys() + ..defaultEncryptionPrivateKey = decryptedEncryptionPrivateKey + ..defaultEncryptionPublicKey = defaultEncryptionPublicKey + ..apkamSymmetricKey = apkamSymmetricKey + ..defaultSelfEncryptionKey = decryptedSelfEncryptionKey + ..apkamPublicKey = apkamKeyPair.publicKey.toString() + ..apkamPrivateKey = apkamKeyPair.privateKey.toString(); + logger.finer('Generating keys file for $enrollmentIdFromServer'); + await _generateAtKeysFile(enrollmentIdFromServer, atSecurityKeys); + }); + } + + // Future _pkamSuccessCallback( + // AtLookupImpl atLookUpImpl, + // String enrollmentIdFromServer, + // String apkamSymmetricKey, + // String defaultEncryptionPublicKey, + // RSAKeypair apkamKeyPair) async { + // //1. Retrieve encrypted keys from server + // var decryptedEncryptionPrivateKey = EncryptionUtil.decryptValue( + // await _getEncryptionPrivateKeyFromServer( + // enrollmentIdFromServer, atLookUpImpl), + // apkamSymmetricKey); + // var decryptedSelfEncryptionKey = EncryptionUtil.decryptValue( + // await _getSelfEncryptionKeyFromServer( + // enrollmentIdFromServer, atLookUpImpl), + // apkamSymmetricKey); + // + // //2. Save security keys and enrollmentId in atKeys file + // var atSecurityKeys = AtSecurityKeys() + // ..defaultEncryptionPrivateKey = decryptedEncryptionPrivateKey + // ..defaultEncryptionPublicKey = defaultEncryptionPublicKey + // ..apkamSymmetricKey = apkamSymmetricKey + // ..defaultSelfEncryptionKey = decryptedSelfEncryptionKey + // ..apkamPublicKey = apkamKeyPair.publicKey.toString() + // ..apkamPrivateKey = apkamKeyPair.privateKey.toString(); + // await _generateAtKeysFile(enrollmentIdFromServer, atSecurityKeys); + // } + Future _getEncryptionPrivateKeyFromServer( String enrollmentIdFromServer, AtLookUp atLookUp) async { var privateKeyCommand = 'keys:get:keyName:$enrollmentIdFromServer.$defaultEncryptionPrivateKey.__manage$_atSign\n'; - var encryptionPrivateKeyFromServer; + String encryptionPrivateKeyFromServer; try { var getPrivateKeyResult = await atLookUp.executeCommand(privateKeyCommand, auth: true); @@ -244,7 +287,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { String enrollmentIdFromServer, AtLookUp atLookUp) async { var selfEncryptionKeyCommand = 'keys:get:keyName:$enrollmentIdFromServer.$defaultSelfEncryptionKey.__manage$_atSign\n'; - var selfEncryptionKeyFromServer; + String selfEncryptionKeyFromServer; try { var getSelfEncryptionKeyResult = await atLookUp.executeCommand(selfEncryptionKeyCommand, auth: true); @@ -264,6 +307,27 @@ class AtOnboardingServiceImpl implements AtOnboardingService { return selfEncryptionKeyFromServer; } + Future _attemptPkamAuthAsync( + AtLookupImpl atLookUpImpl, + String enrollmentIdFromServer, + Duration retryInterval, + String apkamSymmetricKey, + String defaultEncryptionPublicKey, + RSAKeypair apkamKeyPair) async { + while (true) { + logger.finer('Attempting pkam for $enrollmentIdFromServer'); + bool pkamAuthResult = await _attemptPkamAuth( + atLookUpImpl, enrollmentIdFromServer, retryInterval); + if (pkamAuthResult) { + logger.finer('Pkam auth successful for $enrollmentIdFromServer'); + _pkamSuccessController.add(enrollmentIdFromServer); + break; + } + logger.finer('Retrying pkam after mins: $retryInterval'); + await Future.delayed(retryInterval); // Delay and retry + } + } + Future _attemptPkamAuth(AtLookUp atLookUp, String enrollmentIdFromServer, Duration retryInterval) async { try { @@ -289,7 +353,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { ///method to generate/update encryption key-pairs to activate an atsign Future _activateAtsign(AtLookupImpl atLookUpImpl) async { //1. Generate pkam key pair(if authMode is keyFile), encryption key pair, self encryption key and apkam symmetric key pair - AtSecurityKeys atSecurityKeys = await _generateKeyPairs(); + AtSecurityKeys atSecurityKeys = _generateKeyPairs(); var enrollBuilder = EnrollVerbBuilder() ..appName = atOnboardingPreference.appName @@ -367,6 +431,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { stdout.writeln('[Success]----------atSign activated---------'); stdout.writeln('-----------------saving atkeys file---------'); await _generateAtKeysFile(enrollmentIdFromServer, atSecurityKeys); + await _persistKeysLocalSecondary(); } } @@ -526,15 +591,13 @@ class AtOnboardingServiceImpl implements AtOnboardingService { 'EncryptionPrivateKey persist to localSecondary: status $response'); response = await _atClient?.getLocalSecondary()?.putValue( AT_ENCRYPTION_SELF_KEY, atSecurityKeys.defaultSelfEncryptionKey!); - logger - .finer('SelfEncryptionKey persist to localSecondary: status $response'); } @override Future authenticate({String? enrollmentId}) async { // decrypts all the keys in .atKeysFile using the SelfEncryptionKey // and stores the keys in a map - var atSecurityKeys = await _decryptAtKeysFile( + var atSecurityKeys = _decryptAtKeysFile( await _readAtKeysFile(atOnboardingPreference.atKeysFilePath)); var pkamPrivateKey = atSecurityKeys.apkamPrivateKey; @@ -545,7 +608,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { '${atOnboardingPreference.atKeysFilePath}. Please provide a valid atKeys file', exceptionScenario: ExceptionScenario.invalidValueProvided); } - await _init(atSecurityKeys); + await _init(atSecurityKeys, enrollmentId: enrollmentId); logger.finer('Authenticating using PKAM'); try { _isPkamAuthenticated = @@ -560,6 +623,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { if (!_isAtsignOnboarded && _isPkamAuthenticated && atOnboardingPreference.atKeysFilePath != null) { + logger.finer('Calling persist keys to local secondary'); await _persistKeysLocalSecondary(); } diff --git a/packages/at_onboarding_cli/pubspec.yaml b/packages/at_onboarding_cli/pubspec.yaml index 37b83a64..ec1020da 100644 --- a/packages/at_onboarding_cli/pubspec.yaml +++ b/packages/at_onboarding_cli/pubspec.yaml @@ -15,11 +15,11 @@ executables: dependencies: at_utils: ^3.0.12 at_client: ^3.0.59 - at_lookup: ^3.0.36 + at_lookup: ^3.0.39 zxing2: ^0.2.0 image: ^4.0.17 crypton: ^2.0.3 - at_commons: ^3.0.45 + at_commons: ^3.0.53 encrypt: ^5.0.1 at_server_status: ^1.0.3 path: ^1.8.1 @@ -27,15 +27,15 @@ dependencies: http: ^0.13.6 at_chops: ^1.0.3 +#TODO replace with published versions before merging dependency_overrides: - at_chops: - path: ../at_chops - at_lookup: - path: ../at_lookup at_commons: path: ../at_commons at_client: - path: ../../../at_client_sdk/packages/at_client + git: + url: https://github.com/atsign-foundation/at_client_sdk + ref: trunk + path: packages/at_client dev_dependencies: diff --git a/tests/at_onboarding_cli_functional_tests/pubspec.yaml b/tests/at_onboarding_cli_functional_tests/pubspec.yaml index fa1b19af..073b1c74 100644 --- a/tests/at_onboarding_cli_functional_tests/pubspec.yaml +++ b/tests/at_onboarding_cli_functional_tests/pubspec.yaml @@ -9,11 +9,21 @@ environment: dependencies: at_onboarding_cli: path: ../../packages/at_onboarding_cli - at_commons: ^3.0.44 - at_utils: ^3.0.12 - at_lookup: ^3.0.36 + at_commons: ^3.0.53 + at_utils: ^3.0.15 + at_lookup: ^3.0.39 at_client: ^3.0.58 +#TODO replace with published versions before merging +dependency_overrides: + at_commons: + path: ../../../at_libraries/packages/at_commons + at_client: + git: + url: https://github.com/atsign-foundation/at_client_sdk + ref: trunk + path: packages/at_client + dev_dependencies: lints: ^1.0.0 test: ^1.17.2 diff --git a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart new file mode 100644 index 00000000..f4976ed0 --- /dev/null +++ b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart @@ -0,0 +1,175 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:at_client/at_client.dart'; +import 'package:at_demo_data/at_demo_data.dart' as at_demos; +import 'package:at_onboarding_cli/at_onboarding_cli.dart'; +import 'package:at_utils/at_utils.dart'; +import 'package:test/test.dart'; + +String atSign = '@aliceđź› '; +var pkamPublicKey; +var pkamPrivateKey; +var encryptionPublicKey; +var encryptionPrivateKey; +var selfEncryptionKey; +void main() { + AtSignLogger.root_level = 'info'; + final logger = AtSignLogger('OnboardingEnrollmentTest'); + group('A group of tests to assert on authenticate functionality', () { + test('A test to verify authentication and enroll request', () async { + //1. Onboard first client + AtOnboardingPreference preference_1 = getPreferenceForAuth(atSign); + AtOnboardingService? onboardingService_1 = + AtOnboardingServiceImpl(atSign, preference_1); + bool status = await onboardingService_1.onboard(); + expect(status, true); + preference_1.privateKey = pkamPrivateKey; + + //2. authenticate first client + await onboardingService_1.authenticate(); + await _setLastReceivedNotificationDateTime(onboardingService_1.atClient!); + + //3. run totp:get from first client + String? totp = await onboardingService_1.atClient! + .getRemoteSecondary()! + .executeCommand('totp:get\n', auth: true); + totp = totp!.replaceFirst('data:', ''); + totp = totp.trim(); + logger.finer('otp: $totp'); + Map namespaces = {"buzz": "rw"}; + + //4. enroll second client + AtOnboardingPreference enrollPreference_2 = + getPreferenceForEnroll(atSign); + final onboardingService_2 = + AtOnboardingServiceImpl(atSign, enrollPreference_2); + var enrollResponse = + await onboardingService_2.enroll('buzz', 'iphone', totp, namespaces); + logger.finer('enroll response $enrollResponse'); + var completer = Completer(); // Create a Completer + + //5. listen for notification from first client and invoke callback which approves the enrollment + onboardingService_1.atClient!.notificationService + .subscribe(regex: '.__manage') + .listen(expectAsync1((notification) async { + logger.finer('got enroll notificaiton'); + await _notificationCallback( + notification, onboardingService_1.atClient!); + completer.complete(); + }, count: 1, max: -1)); + await completer.future; + + //6. assert that the keys file is created for enrolled app + final enrolledClientKeysFile = File(enrollPreference_2.atKeysFilePath!); + while (!await enrolledClientKeysFile.exists()) { + await Future.delayed(Duration(seconds: 10)); + } + expect(await enrolledClientKeysFile.exists(), true); + enrolledClientKeysFile.deleteSync(); + }, timeout: Timeout(Duration(minutes: 3))); + }); +} + +Future _notificationCallback( + AtNotification notification, AtClient atClient) async { + print('enroll notification received: ${notification.toString()}'); + final notificationKey = notification.key; + final enrollmentId = + notificationKey.substring(0, notificationKey.indexOf('.new.enrollments')); + var enrollRequest; + var enrollParamsJson = {}; + enrollParamsJson['enrollmentId'] = enrollmentId; + final encryptedApkamSymmetricKey = + jsonDecode(notification.value!)['encryptedApkamSymmetricKey']; + var encryptionPrivateKey = + await atClient.getLocalSecondary()!.getEncryptionPrivateKey()!; + var selfEncryptionKey = + await atClient.getLocalSecondary()!.getEncryptionSelfKey(); + final apkamSymmetricKey = EncryptionUtil.decryptKey( + encryptedApkamSymmetricKey, encryptionPrivateKey); + var encryptedDefaultPrivateEncKey = + EncryptionUtil.encryptValue(encryptionPrivateKey, apkamSymmetricKey); + var encryptedDefaultSelfEncKey = + EncryptionUtil.encryptValue(selfEncryptionKey!, apkamSymmetricKey); + enrollParamsJson['encryptedDefaultEncryptedPrivateKey'] = + encryptedDefaultPrivateEncKey; + enrollParamsJson['encryptedDefaultSelfEncryptionKey'] = + encryptedDefaultSelfEncKey; + enrollRequest = 'enroll:approve:${jsonEncode(enrollParamsJson)}\n'; + print('enroll approval request to server: $enrollRequest'); + String? enrollResponse = await atClient + .getRemoteSecondary()! + .executeCommand(enrollRequest, auth: true); + print('enroll approval Response: $enrollResponse'); +} + +Future _setLastReceivedNotificationDateTime(AtClient atClient) async { + var lastReceivedNotificationAtKey = AtKey.local( + 'lastreceivednotification', atClient.getCurrentAtSign()!, + namespace: atClient.getPreferences()!.namespace) + .build(); + + var atNotification = AtNotification( + '124', + '@bobđź› :testnotificationkey', + '@alice', + '@bobđź› ', + DateTime.now().millisecondsSinceEpoch, + MessageTypeEnum.text.toString(), + true); + + await atClient.put( + lastReceivedNotificationAtKey, jsonEncode(atNotification.toJson())); +} + +AtOnboardingPreference getPreferenceForAuth(String atSign) { + atSign = AtUtils.fixAtSign(atSign); + AtOnboardingPreference atOnboardingPreference = AtOnboardingPreference() + ..rootDomain = 'vip.ve.atsign.zone' + ..isLocalStoreRequired = true + ..hiveStoragePath = 'storage/hive/client' + ..commitLogPath = 'storage/hive/client/commit' + ..cramSecret = at_demos.cramKeyMap[atSign] + ..namespace = + 'wavi' // unique identifier that can be used to identify data from your app + ..atKeysFilePath = + '${Platform.environment['HOME']}/.atsign/keys/${atSign}_key.atKeys' + ..appName = 'wavi' + ..deviceName = 'pixel' + ..rootDomain = 'vip.ve.atsign.zone'; + + return atOnboardingPreference; +} + +AtOnboardingPreference getPreferenceForEnroll(String atSign) { + atSign = AtUtils.fixAtSign(atSign); + AtOnboardingPreference atOnboardingPreference = AtOnboardingPreference() + ..namespace = + 'buzz' // unique identifier that can be used to identify data from your app + ..atKeysFilePath = + '${Platform.environment['HOME']}/.atsign/keys/${atSign}_buzzkey.atKeys' + ..appName = 'buzz' + ..deviceName = 'iphone' + ..rootDomain = 'vip.ve.atsign.zone' + ..apkamAuthRetryDurationMins = 1; + return atOnboardingPreference; +} + +Future getAtKeys() async { + AtOnboardingPreference preference = getPreferenceForAuth(atSign); + String? filePath = preference.atKeysFilePath; + var fileContents = File(filePath!).readAsStringSync(); + var keysJSON = json.decode(fileContents); + selfEncryptionKey = keysJSON['selfEncryptionKey']; + + pkamPublicKey = EncryptionUtil.decryptValue( + keysJSON['aesPkamPublicKey'], selfEncryptionKey); + pkamPrivateKey = EncryptionUtil.decryptValue( + keysJSON['aesPkamPrivateKey'], selfEncryptionKey); + encryptionPublicKey = EncryptionUtil.decryptValue( + keysJSON['aesEncryptPublicKey'], selfEncryptionKey); + encryptionPrivateKey = EncryptionUtil.decryptValue( + keysJSON['aesEncryptPrivateKey'], selfEncryptionKey); +} From fce95705b6d079c226971ebcf6c821957770a019 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Tue, 29 Aug 2023 15:51:09 +0530 Subject: [PATCH 06/35] fix: analyzer issue and func test failure fix --- .../lib/src/onboard/at_onboarding_service_impl.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 6a8e1625..f5c314a2 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 @@ -637,7 +637,9 @@ class AtOnboardingServiceImpl implements AtOnboardingService { final atPkamKeyPair = AtPkamKeyPair.create( atKeysFile.apkamPublicKey!, atKeysFile.apkamPrivateKey!); final atChopsKeys = AtChopsKeys.create(atEncryptionKeyPair, atPkamKeyPair); - atChopsKeys.apkamSymmetricKey = AESKey(atKeysFile.apkamSymmetricKey!); + if (atKeysFile.apkamSymmetricKey != null) { + atChopsKeys.apkamSymmetricKey = AESKey(atKeysFile.apkamSymmetricKey!); + } atChopsKeys.selfEncryptionKey = AESKey(atKeysFile.defaultSelfEncryptionKey!); return AtChopsImpl(atChopsKeys); From ae06b94914055c8a602b03f35f350536cbf995d3 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Tue, 29 Aug 2023 20:05:23 +0530 Subject: [PATCH 07/35] fix: remove pkam load for functional test, manually create keys inside tests --- .github/workflows/at_libraries.yaml | 5 +-- .../example/apkam_enroll.dart | 5 ++- .../test/at_onboarding_cli_test.dart | 31 ++++++++++++++++--- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/.github/workflows/at_libraries.yaml b/.github/workflows/at_libraries.yaml index 603f7c9b..b5c3b63b 100644 --- a/.github/workflows/at_libraries.yaml +++ b/.github/workflows/at_libraries.yaml @@ -101,8 +101,9 @@ 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 }} diff --git a/packages/at_onboarding_cli/example/apkam_enroll.dart b/packages/at_onboarding_cli/example/apkam_enroll.dart index f30cf1d3..df10d2f3 100644 --- a/packages/at_onboarding_cli/example/apkam_enroll.dart +++ b/packages/at_onboarding_cli/example/apkam_enroll.dart @@ -1,5 +1,3 @@ -import 'package:at_client/at_client.dart'; -import 'package:at_lookup/at_lookup.dart'; import 'package:at_onboarding_cli/at_onboarding_cli.dart'; import 'package:at_utils/at_logger.dart'; @@ -17,6 +15,7 @@ Future main() async { 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); + var enrollmentResponse = + await onboardingService.enroll('buzz', 'iphone', "068881", namespaces); print('enrollmentResponse: $enrollmentResponse'); } diff --git a/tests/at_onboarding_cli_functional_tests/test/at_onboarding_cli_test.dart b/tests/at_onboarding_cli_functional_tests/test/at_onboarding_cli_test.dart index ca2af356..1a9993e0 100644 --- a/tests/at_onboarding_cli_functional_tests/test/at_onboarding_cli_test.dart +++ b/tests/at_onboarding_cli_functional_tests/test/at_onboarding_cli_test.dart @@ -11,15 +11,33 @@ import 'package:at_utils/at_utils.dart'; import 'package:test/test.dart'; final String atKeysFilePath = '${Platform.environment['HOME']}/.atsign/keys'; - +Map keysCreatedMap = {}; void main() { AtSignLogger.root_level = 'finest'; + // These group of tests run on docker container with only cram key available on secondary + // Perform cram auth and update keys manually. + Future _createKeys(String atSign) async { + if (keysCreatedMap.containsKey(atSign)) { + return; + } + var atLookup = AtLookupImpl(atSign, 'vip.ve.atsign.zone', 64); + await atLookup.authenticate_cram(at_demos.cramKeyMap[atSign]); + var command = + 'update:privatekey:at_pkam_publickey ${at_demos.pkamPublicKeyMap[atSign]}\n'; + var response = await atLookup.executeCommand(command, auth: true); + expect(response, 'data:-1'); + command = + 'update:public:publickey${atSign} ${at_demos.encryptionPublicKeyMap[atSign]}\n'; + await atLookup.executeCommand(command, auth: true); + keysCreatedMap[atSign] = true; + await atLookup.close(); + } + group('A group of tests to assert on authenticate functionality', () { test('A test to verify authentication is successful with .atKeys file', () async { - // Intentionally '@' is not prefixed. - // AtOnboardingServiceImpl call's fixAtSign which prefixes '@' - String atSign = 'aliceđź› '; + String atSign = '@aliceđź› '; + await _createKeys(atSign); AtOnboardingPreference preference = getPreferences(atSign); await generateAtKeysFile(atSign, preference.atKeysFilePath!); AtOnboardingService atOnboardingService = @@ -32,6 +50,7 @@ void main() { 'A test to verify update and llookup verbs with authenticated atLookup instance', () async { String atSign = '@aliceđź› '; + await _createKeys(atSign); AtOnboardingPreference preference = getPreferences(atSign); await generateAtKeysFile(atSign, preference.atKeysFilePath!); AtOnboardingService atOnboardingService = @@ -49,6 +68,7 @@ void main() { 'A test to authenticate and atSign and invoke AtClient put and get methods', () async { String atSign = '@eveđź› '; + await _createKeys(atSign); AtOnboardingPreference preference = getPreferences(atSign); await generateAtKeysFile(atSign, preference.atKeysFilePath!); AtOnboardingService onboardingService = @@ -70,8 +90,8 @@ void main() { preference.atKeysFilePath = null; AtOnboardingServiceImpl(atSign, preference); expect(preference.atKeysFilePath, '$atKeysFilePath/${atSign}_key.atKeys'); - }); + tearDown(() async { await tearDownFunc(); }); @@ -90,6 +110,7 @@ void main() { 'A test to authenticate atSign and verify PKAM keys and encryption keys are updated to local secondary', () async { await generateAtKeysFile(atSign, atOnboardingPreference.atKeysFilePath!); + await _createKeys(atSign); bool status = await atOnboardingService.authenticate(); atClient = await atOnboardingService.atClient; expect(true, status); From 98e45e37bfdfb20e90b3160ec1fb9d3dfb46b468 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Tue, 29 Aug 2023 20:16:54 +0530 Subject: [PATCH 08/35] fix: comment check_test_env readiness --- .github/workflows/at_libraries.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/at_libraries.yaml b/.github/workflows/at_libraries.yaml index b5c3b63b..59c885bd 100644 --- a/.github/workflows/at_libraries.yaml +++ b/.github/workflows/at_libraries.yaml @@ -105,9 +105,9 @@ jobs: # - 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 }} From ae07f319e184daa633e795b98778ca8620964089 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Tue, 29 Aug 2023 20:28:18 +0530 Subject: [PATCH 09/35] fix: change atsign for onboard test --- .../test/enrollment_test.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart index f4976ed0..310aa515 100644 --- a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart +++ b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart @@ -8,7 +8,6 @@ import 'package:at_onboarding_cli/at_onboarding_cli.dart'; import 'package:at_utils/at_utils.dart'; import 'package:test/test.dart'; -String atSign = '@aliceđź› '; var pkamPublicKey; var pkamPrivateKey; var encryptionPublicKey; @@ -19,6 +18,9 @@ void main() { final logger = AtSignLogger('OnboardingEnrollmentTest'); group('A group of tests to assert on authenticate functionality', () { test('A test to verify authentication and enroll request', () async { + // if onboard is testing use distinct demo atsign per test, + // since cram keys get deleted on server for already onboarded atsign + String atSign = '@nareshđź› '; //1. Onboard first client AtOnboardingPreference preference_1 = getPreferenceForAuth(atSign); AtOnboardingService? onboardingService_1 = @@ -114,7 +116,7 @@ Future _setLastReceivedNotificationDateTime(AtClient atClient) async { var atNotification = AtNotification( '124', '@bobđź› :testnotificationkey', - '@alice', + '@nareshđź› ', '@bobđź› ', DateTime.now().millisecondsSinceEpoch, MessageTypeEnum.text.toString(), @@ -157,7 +159,7 @@ AtOnboardingPreference getPreferenceForEnroll(String atSign) { return atOnboardingPreference; } -Future getAtKeys() async { +Future getAtKeys(String atSign) async { AtOnboardingPreference preference = getPreferenceForAuth(atSign); String? filePath = preference.atKeysFilePath; var fileContents = File(filePath!).readAsStringSync(); From a9d0ab13bd4b5171737fcfe765de2000e1762841 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Wed, 30 Aug 2023 15:25:53 +0530 Subject: [PATCH 10/35] fix: added onboarding functional tests readme, minor changes --- .../onboard/at_onboarding_service_impl.dart | 2 +- .../src/util/at_onboarding_preference.dart | 4 +-- .../README.md | 19 ++++++++++ .../test/at_onboarding_cli_test.dart | 36 +++---------------- .../test/enrollment_test.dart | 6 ++-- 5 files changed, 30 insertions(+), 37 deletions(-) create mode 100644 tests/at_onboarding_cli_functional_tests/README.md 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 f5c314a2..aab4232b 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 @@ -95,7 +95,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { if (atOnboardingPreference.appName == null || atOnboardingPreference.deviceName == null) { throw AtOnboardingException( - 'appName and deviceName are mandatory for onboarding'); + 'appName and deviceName are mandatory for onboarding. Please set the params in AtOnboardingPreference'); } // cram auth doesn't use at_chops. So create at_lookup here. AtLookupImpl atLookUpImpl = AtLookupImpl(_atSign, diff --git a/packages/at_onboarding_cli/lib/src/util/at_onboarding_preference.dart b/packages/at_onboarding_cli/lib/src/util/at_onboarding_preference.dart index 3d7d9358..db464c10 100644 --- a/packages/at_onboarding_cli/lib/src/util/at_onboarding_preference.dart +++ b/packages/at_onboarding_cli/lib/src/util/at_onboarding_preference.dart @@ -28,9 +28,9 @@ class AtOnboardingPreference extends AtClientPreference { /// the hostName of the registrar which will be used to activate the atsign String registrarUrl = RegistrarApiConstants.apiHostProd; - late String appName; + String? appName; - late String deviceName; + String? deviceName; int apkamAuthRetryDurationMins = 30; } diff --git a/tests/at_onboarding_cli_functional_tests/README.md b/tests/at_onboarding_cli_functional_tests/README.md new file mode 100644 index 00000000..6580ffff --- /dev/null +++ b/tests/at_onboarding_cli_functional_tests/README.md @@ -0,0 +1,19 @@ + +Please read these instructions before adding new/modifying onboarding functional tests + +* If a secondary server is started with demo atsigns and pkam/encryption keys loaded,onboard will + return an exception since the server is already in activated state. +* In order to test onboard, we need a server with only cram key available +* Hence pkamLoad script for onboarding functional tests is commented in .github/workflows/at_libraries.yaml +* To test authenticate method in AtOnboardingService, we need the pkam/encryption keys updated in server. + This step is performed within the test before testing authenticate method. + Check _createKeys() method in at_onboarding_cli_test.dart. + You can use demo keys/generate keys file using demo data to test authenticate method. +* To test onboard method in AtOnboardingService, new key pairs will be generated during onboard flow. + Hence demo keys cannot be used to test onboard method. + Use distinct atsign per test method to test onboarding since repeated run of onboard for same atsign + will fail with atsign already activated exception. + Delete the .atKeys file generated during onboard at the end of the test. + e.g enrollment_test.dart +* If you are running onboarding_cli functional tests in local setup,use virtual environment without pkamLoad + diff --git a/tests/at_onboarding_cli_functional_tests/test/at_onboarding_cli_test.dart b/tests/at_onboarding_cli_functional_tests/test/at_onboarding_cli_test.dart index 1a9993e0..25b1a044 100644 --- a/tests/at_onboarding_cli_functional_tests/test/at_onboarding_cli_test.dart +++ b/tests/at_onboarding_cli_functional_tests/test/at_onboarding_cli_test.dart @@ -5,8 +5,6 @@ import 'package:at_client/at_client.dart'; import 'package:at_demo_data/at_demo_data.dart' as at_demos; import 'package:at_lookup/at_lookup.dart'; import 'package:at_onboarding_cli/at_onboarding_cli.dart'; -import 'package:at_onboarding_cli/src/activate_cli/activate_cli.dart' - as activate_cli; import 'package:at_utils/at_utils.dart'; import 'package:test/test.dart'; @@ -151,37 +149,9 @@ void main() { /// Assert .atKeys file is generated for the atSign expect(await File(atOnboardingPreference.atKeysFilePath!).exists(), true); - }, skip: true); - - tearDown(() async { - await tearDownFunc(); }); - }); - - group('A group of tests to verify activate_cli', () { - String atSign = '@colinđź› '; - test( - 'A test to verify atSign is activated and .atKeys file is generated using activate_cli', - () async { - List args = [ - '-a', - atSign, - '-c', - at_demos.cramKeyMap[atSign]!, - '-r', - 'vip.ve.atsign.zone' - ]; - await activate_cli.main(args); - expect(await File('$atKeysFilePath/${atSign}_key.atKeys').exists(), true); - - // Authenticate atSign with the .atKeys file generated via the activate_cli tool. - AtOnboardingPreference atOnboardingPreference = getPreferences(atSign); - AtOnboardingService onboardingService = - AtOnboardingServiceImpl(atSign, atOnboardingPreference); - expect(await onboardingService.authenticate(), true); - }, skip: true); - tearDownAll(() async { + tearDown(() async { await tearDownFunc(); }); }); @@ -197,7 +167,9 @@ AtOnboardingPreference getPreferences(String atSign) { ..privateKey = null ..cramSecret = at_demos.cramKeyMap[atSign] ..atKeysFilePath = '$atKeysFilePath/${atSign}_key.atKeys' - ..downloadPath = atKeysFilePath; + ..downloadPath = atKeysFilePath + ..appName = 'wavi' + ..deviceName = 'pixel'; return atOnboardingPreference; } diff --git a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart index 310aa515..7ac533ac 100644 --- a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart +++ b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart @@ -17,7 +17,9 @@ void main() { AtSignLogger.root_level = 'info'; final logger = AtSignLogger('OnboardingEnrollmentTest'); group('A group of tests to assert on authenticate functionality', () { - test('A test to verify authentication and enroll request', () async { + test( + 'A test to verify send enroll request, approve enrollment and auth by enrollmentId', + () async { // if onboard is testing use distinct demo atsign per test, // since cram keys get deleted on server for already onboarded atsign String atSign = '@nareshđź› '; @@ -56,7 +58,7 @@ void main() { onboardingService_1.atClient!.notificationService .subscribe(regex: '.__manage') .listen(expectAsync1((notification) async { - logger.finer('got enroll notificaiton'); + logger.finer('got enroll notification'); await _notificationCallback( notification, onboardingService_1.atClient!); completer.complete(); From e4dee079a06fda9d70d8c35769fd0fec13318c64 Mon Sep 17 00:00:00 2001 From: purnimavenkatasubbu Date: Thu, 31 Aug 2023 17:01:05 +0530 Subject: [PATCH 11/35] added denial test and minor change --- .../onboard/at_onboarding_service_impl.dart | 2 +- .../pubspec.yaml | 2 + .../test/enrollment_test.dart | 82 +++++++++++++++++-- 3 files changed, 77 insertions(+), 9 deletions(-) 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 aab4232b..11033c89 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 @@ -340,7 +340,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { if (e.message.contains('error:AT0401')) { logger.finer('Retrying pkam auth'); await Future.delayed(retryInterval); - } else if (e.message.contains('error:AT0402')) { + } else if (e.message.contains('error:AT0025')) { //# TODO change the error code once server bug is fixed logger.finer( 'enrollmentId $enrollmentIdFromServer denied.Exiting pkam retry logic'); diff --git a/tests/at_onboarding_cli_functional_tests/pubspec.yaml b/tests/at_onboarding_cli_functional_tests/pubspec.yaml index 073b1c74..16bf4716 100644 --- a/tests/at_onboarding_cli_functional_tests/pubspec.yaml +++ b/tests/at_onboarding_cli_functional_tests/pubspec.yaml @@ -23,6 +23,8 @@ dependency_overrides: url: https://github.com/atsign-foundation/at_client_sdk ref: trunk path: packages/at_client + at_lookup: + path: ../../../at_libraries/packages/at_lookup dev_dependencies: lints: ^1.0.0 diff --git a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart index 7ac533ac..7a961076 100644 --- a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart +++ b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart @@ -33,7 +33,8 @@ void main() { //2. authenticate first client await onboardingService_1.authenticate(); - await _setLastReceivedNotificationDateTime(onboardingService_1.atClient!); + await _setLastReceivedNotificationDateTime( + onboardingService_1.atClient!, atSign); //3. run totp:get from first client String? totp = await onboardingService_1.atClient! @@ -52,6 +53,8 @@ void main() { var enrollResponse = await onboardingService_2.enroll('buzz', 'iphone', totp, namespaces); logger.finer('enroll response $enrollResponse'); + // enrollment id from the response + var enrollmentId = enrollResponse.enrollmentId; var completer = Completer(); // Create a Completer //5. listen for notification from first client and invoke callback which approves the enrollment @@ -60,7 +63,7 @@ void main() { .listen(expectAsync1((notification) async { logger.finer('got enroll notification'); await _notificationCallback( - notification, onboardingService_1.atClient!); + notification, onboardingService_1.atClient!, 'approve'); completer.complete(); }, count: 1, max: -1)); await completer.future; @@ -71,13 +74,70 @@ void main() { await Future.delayed(Duration(seconds: 10)); } expect(await enrolledClientKeysFile.exists(), true); + // Authenticate now with the approved enrollmentID + // assert that authentication is successful + bool authResultWithEnrollment = + await onboardingService_2.authenticate(enrollmentId: enrollmentId); + expect(authResultWithEnrollment, true); enrolledClientKeysFile.deleteSync(); }, timeout: Timeout(Duration(minutes: 3))); }); + + test( + 'A test to verify send enroll request, deny enrollment and auth by enrollmentId should fail', + () async { + // if onboard is testing use distinct demo atsign per test, + // since cram keys get deleted on server for already onboarded atsign + String atSign = '@purnimađź› '; + //1. Onboard first client + AtOnboardingPreference preference_1 = getPreferenceForAuth(atSign); + AtOnboardingService? onboardingService_1 = + AtOnboardingServiceImpl(atSign, preference_1); + bool status = await onboardingService_1.onboard(); + expect(status, true); + preference_1.privateKey = pkamPrivateKey; + + //2. authenticate first client + await onboardingService_1.authenticate(); + await _setLastReceivedNotificationDateTime( + onboardingService_1.atClient!, atSign); + + //3. run totp:get from first client + String? totp = await onboardingService_1.atClient! + .getRemoteSecondary()! + .executeCommand('totp:get\n', auth: true); + totp = totp!.replaceFirst('data:', ''); + totp = totp.trim(); + logger.finer('otp: $totp'); + Map namespaces = {"buzz": "rw"}; + + //4. enroll second client + AtOnboardingPreference enrollPreference_2 = getPreferenceForEnroll(atSign); + final onboardingService_2 = + AtOnboardingServiceImpl(atSign, enrollPreference_2); + + var enrollResponse = + await onboardingService_2.enroll('buzz', 'iphone', totp, namespaces); + logger.finer('enroll response $enrollResponse'); + + var completer = Completer(); // Create a Completer + + //5. listen for notification from first client and invoke callback which denies the enrollment + onboardingService_1.atClient!.notificationService + .subscribe(regex: '.__manage') + .listen(expectAsync1((notification) async { + logger.finer('got enroll notification'); + await _notificationCallback( + notification, onboardingService_1.atClient!, 'deny'); + completer.complete(); + }, count: 1, max: -1)); + await completer.future; + // TODO - assert for atEnrollmentException + }, timeout: Timeout(Duration(minutes: 10))); } Future _notificationCallback( - AtNotification notification, AtClient atClient) async { + AtNotification notification, AtClient atClient, String response) async { print('enroll notification received: ${notification.toString()}'); final notificationKey = notification.key; final enrollmentId = @@ -101,15 +161,21 @@ Future _notificationCallback( encryptedDefaultPrivateEncKey; enrollParamsJson['encryptedDefaultSelfEncryptionKey'] = encryptedDefaultSelfEncKey; - enrollRequest = 'enroll:approve:${jsonEncode(enrollParamsJson)}\n'; - print('enroll approval request to server: $enrollRequest'); + if (response == 'approve') { + enrollRequest = 'enroll:approve:${jsonEncode(enrollParamsJson)}\n'; + print('enroll approval request to server: $enrollRequest'); + } else { + enrollRequest = 'enroll:deny:${jsonEncode(enrollParamsJson)}\n'; + print('enroll denial request $enrollRequest'); + } String? enrollResponse = await atClient .getRemoteSecondary()! .executeCommand(enrollRequest, auth: true); - print('enroll approval Response: $enrollResponse'); + print('enroll Response from server: $enrollResponse'); } -Future _setLastReceivedNotificationDateTime(AtClient atClient) async { +Future _setLastReceivedNotificationDateTime( + AtClient atClient, String atSign) async { var lastReceivedNotificationAtKey = AtKey.local( 'lastreceivednotification', atClient.getCurrentAtSign()!, namespace: atClient.getPreferences()!.namespace) @@ -118,7 +184,7 @@ Future _setLastReceivedNotificationDateTime(AtClient atClient) async { var atNotification = AtNotification( '124', '@bobđź› :testnotificationkey', - '@nareshđź› ', + atSign, '@bobđź› ', DateTime.now().millisecondsSinceEpoch, MessageTypeEnum.text.toString(), From bc10f21ecddf291d31fb84a66b445d2d3b683e00 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Thu, 7 Sep 2023 22:59:39 +0530 Subject: [PATCH 12/35] fix: change totp to otp --- .../at_commons/lib/src/enroll/enrollment.dart | 1 + .../src/onboard/at_onboarding_service.dart | 4 +-- .../onboard/at_onboarding_service_impl.dart | 35 +++---------------- .../lib/src/onboard/at_security_keys.dart | 2 +- packages/at_onboarding_cli/pubspec.yaml | 12 +++---- 5 files changed, 13 insertions(+), 41 deletions(-) diff --git a/packages/at_commons/lib/src/enroll/enrollment.dart b/packages/at_commons/lib/src/enroll/enrollment.dart index 04a94acb..bfb68f57 100644 --- a/packages/at_commons/lib/src/enroll/enrollment.dart +++ b/packages/at_commons/lib/src/enroll/enrollment.dart @@ -1,6 +1,7 @@ class EnrollResponse { String enrollmentId; EnrollStatus enrollStatus; + EnrollResponse(this.enrollmentId, this.enrollStatus); @override diff --git a/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart b/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart index ae2e087b..adf25d86 100644 --- a/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart +++ b/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart @@ -16,11 +16,11 @@ abstract class AtOnboardingService { /// 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 - /// totp - otp retrieved from an already enrolled app + /// 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 enroll(String appName, String deviceName, String totp, + Future enroll(String appName, String deviceName, String otp, Map namespaces, {int? pkamRetryIntervalMins}); 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 11033c89..b7a7f59e 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 @@ -150,7 +150,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { } @override - Future enroll(String appName, String deviceName, String totp, + Future enroll(String appName, String deviceName, String otp, Map namespaces, {int? pkamRetryIntervalMins}) async { if (appName == null || deviceName == null) { @@ -177,7 +177,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { var enrollmentResponse = await _sendEnrollRequest( appName, deviceName, - totp, + otp, namespaces, apkamKeyPair.publicKey.toString(), encryptedApkamSymmetricKey, @@ -235,33 +235,6 @@ class AtOnboardingServiceImpl implements AtOnboardingService { }); } - // Future _pkamSuccessCallback( - // AtLookupImpl atLookUpImpl, - // String enrollmentIdFromServer, - // String apkamSymmetricKey, - // String defaultEncryptionPublicKey, - // RSAKeypair apkamKeyPair) async { - // //1. Retrieve encrypted keys from server - // var decryptedEncryptionPrivateKey = EncryptionUtil.decryptValue( - // await _getEncryptionPrivateKeyFromServer( - // enrollmentIdFromServer, atLookUpImpl), - // apkamSymmetricKey); - // var decryptedSelfEncryptionKey = EncryptionUtil.decryptValue( - // await _getSelfEncryptionKeyFromServer( - // enrollmentIdFromServer, atLookUpImpl), - // apkamSymmetricKey); - // - // //2. Save security keys and enrollmentId in atKeys file - // var atSecurityKeys = AtSecurityKeys() - // ..defaultEncryptionPrivateKey = decryptedEncryptionPrivateKey - // ..defaultEncryptionPublicKey = defaultEncryptionPublicKey - // ..apkamSymmetricKey = apkamSymmetricKey - // ..defaultSelfEncryptionKey = decryptedSelfEncryptionKey - // ..apkamPublicKey = apkamKeyPair.publicKey.toString() - // ..apkamPrivateKey = apkamKeyPair.privateKey.toString(); - // await _generateAtKeysFile(enrollmentIdFromServer, atSecurityKeys); - // } - Future _getEncryptionPrivateKeyFromServer( String enrollmentIdFromServer, AtLookUp atLookUp) async { var privateKeyCommand = @@ -438,7 +411,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { Future _sendEnrollRequest( String appName, String deviceName, - String totp, + String otp, Map namespaces, String apkamPublicKey, String encryptedApkamSymmetricKey, @@ -447,7 +420,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { ..appName = appName ..deviceName = deviceName ..namespaces = namespaces - ..totp = totp + ..otp = otp ..apkamPublicKey = apkamPublicKey ..encryptedAPKAMSymmetricKey = encryptedApkamSymmetricKey; var enrollResult = diff --git a/packages/at_onboarding_cli/lib/src/onboard/at_security_keys.dart b/packages/at_onboarding_cli/lib/src/onboard/at_security_keys.dart index 8df3c6d4..e307a66c 100644 --- a/packages/at_onboarding_cli/lib/src/onboard/at_security_keys.dart +++ b/packages/at_onboarding_cli/lib/src/onboard/at_security_keys.dart @@ -1,4 +1,4 @@ -/// Holder for different encyrption keys that will be stored in .atKeys file. +/// Holder for different encryption keys that will be stored in .atKeys file. /// Apkam symmetric key, enrollmentId and defaultSelfEncryptionKey will be stored in unencrypted format in .atKeys file. /// All other values will be encrypted before saving to .atKeys file. class AtSecurityKeys { diff --git a/packages/at_onboarding_cli/pubspec.yaml b/packages/at_onboarding_cli/pubspec.yaml index ec1020da..30d26c24 100644 --- a/packages/at_onboarding_cli/pubspec.yaml +++ b/packages/at_onboarding_cli/pubspec.yaml @@ -13,24 +13,22 @@ executables: at_activate: activate_cli dependencies: - at_utils: ^3.0.12 - at_client: ^3.0.59 - at_lookup: ^3.0.39 + at_utils: ^3.0.15 + at_client: ^3.0.64 + at_lookup: ^3.0.40 zxing2: ^0.2.0 image: ^4.0.17 crypton: ^2.0.3 - at_commons: ^3.0.53 + at_commons: ^3.0.54 encrypt: ^5.0.1 at_server_status: ^1.0.3 path: ^1.8.1 args: ^2.4.1 http: ^0.13.6 - at_chops: ^1.0.3 + at_chops: ^1.0.4 #TODO replace with published versions before merging dependency_overrides: - at_commons: - path: ../at_commons at_client: git: url: https://github.com/atsign-foundation/at_client_sdk From 57f3f699b792b132b7b21f66fdc75b76a4309da2 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Thu, 7 Sep 2023 23:07:40 +0530 Subject: [PATCH 13/35] fix: upgrade dependency in func test pubspec --- tests/at_onboarding_cli_functional_tests/pubspec.yaml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/at_onboarding_cli_functional_tests/pubspec.yaml b/tests/at_onboarding_cli_functional_tests/pubspec.yaml index 16bf4716..c4eef74d 100644 --- a/tests/at_onboarding_cli_functional_tests/pubspec.yaml +++ b/tests/at_onboarding_cli_functional_tests/pubspec.yaml @@ -9,22 +9,18 @@ environment: dependencies: at_onboarding_cli: path: ../../packages/at_onboarding_cli - at_commons: ^3.0.53 + at_commons: ^3.0.54 at_utils: ^3.0.15 - at_lookup: ^3.0.39 + at_lookup: ^3.0.40 at_client: ^3.0.58 #TODO replace with published versions before merging dependency_overrides: - at_commons: - path: ../../../at_libraries/packages/at_commons at_client: git: url: https://github.com/atsign-foundation/at_client_sdk ref: trunk path: packages/at_client - at_lookup: - path: ../../../at_libraries/packages/at_lookup dev_dependencies: lints: ^1.0.0 From be40e9a953098218bf0a8690480446a319552671 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Thu, 7 Sep 2023 23:13:51 +0530 Subject: [PATCH 14/35] fix: change totp to otp in tests --- .../test/enrollment_test.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart index 7a961076..0402f91a 100644 --- a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart +++ b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart @@ -36,10 +36,10 @@ void main() { await _setLastReceivedNotificationDateTime( onboardingService_1.atClient!, atSign); - //3. run totp:get from first client + //3. run otp:get from first client String? totp = await onboardingService_1.atClient! .getRemoteSecondary()! - .executeCommand('totp:get\n', auth: true); + .executeCommand('otp:get\n', auth: true); totp = totp!.replaceFirst('data:', ''); totp = totp.trim(); logger.finer('otp: $totp'); @@ -102,10 +102,10 @@ void main() { await _setLastReceivedNotificationDateTime( onboardingService_1.atClient!, atSign); - //3. run totp:get from first client + //3. run otp:get from first client String? totp = await onboardingService_1.atClient! .getRemoteSecondary()! - .executeCommand('totp:get\n', auth: true); + .executeCommand('otp:get\n', auth: true); totp = totp!.replaceFirst('data:', ''); totp = totp.trim(); logger.finer('otp: $totp'); From 0c31261ac01b72805e5108e1e6ec4f5e0f48bfcf Mon Sep 17 00:00:00 2001 From: murali-shris Date: Thu, 14 Sep 2023 12:28:26 +0530 Subject: [PATCH 15/35] fix: remove dependency overrides for at_client and replace with version 3.0.65 --- packages/at_onboarding_cli/pubspec.yaml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/at_onboarding_cli/pubspec.yaml b/packages/at_onboarding_cli/pubspec.yaml index 30d26c24..64cd58a9 100644 --- a/packages/at_onboarding_cli/pubspec.yaml +++ b/packages/at_onboarding_cli/pubspec.yaml @@ -14,12 +14,12 @@ executables: dependencies: at_utils: ^3.0.15 - at_client: ^3.0.64 + at_client: ^3.0.65 at_lookup: ^3.0.40 zxing2: ^0.2.0 image: ^4.0.17 crypton: ^2.0.3 - at_commons: ^3.0.54 + at_commons: ^3.0.55 encrypt: ^5.0.1 at_server_status: ^1.0.3 path: ^1.8.1 @@ -27,15 +27,6 @@ dependencies: http: ^0.13.6 at_chops: ^1.0.4 -#TODO replace with published versions before merging -dependency_overrides: - at_client: - git: - url: https://github.com/atsign-foundation/at_client_sdk - ref: trunk - path: packages/at_client - - dev_dependencies: lints: ^2.1.0 test: ^1.24.2 From b45a246bf6fb4e851bce7ccd0a4383fe84d9c186 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Thu, 14 Sep 2023 13:26:34 +0530 Subject: [PATCH 16/35] fix: replace at_client dependency overrides in func tests pubspec with 3.0.65 --- .../at_onboarding_cli_functional_tests/pubspec.yaml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/tests/at_onboarding_cli_functional_tests/pubspec.yaml b/tests/at_onboarding_cli_functional_tests/pubspec.yaml index c4eef74d..e7f0d13a 100644 --- a/tests/at_onboarding_cli_functional_tests/pubspec.yaml +++ b/tests/at_onboarding_cli_functional_tests/pubspec.yaml @@ -9,18 +9,10 @@ environment: dependencies: at_onboarding_cli: path: ../../packages/at_onboarding_cli - at_commons: ^3.0.54 + at_commons: ^3.0.55 at_utils: ^3.0.15 at_lookup: ^3.0.40 - at_client: ^3.0.58 - -#TODO replace with published versions before merging -dependency_overrides: - at_client: - git: - url: https://github.com/atsign-foundation/at_client_sdk - ref: trunk - path: packages/at_client + at_client: ^3.0.65 dev_dependencies: lints: ^1.0.0 From 41f8b5cf98468f4c25f1ae1e47144ce064307ac9 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Thu, 14 Sep 2023 13:55:22 +0530 Subject: [PATCH 17/35] fix: code comment, reduced timeout in func test from 10 to 5 mins --- .../lib/src/onboard/at_onboarding_service_impl.dart | 1 + .../test/enrollment_test.dart | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) 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 b7a7f59e..e3795caa 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 @@ -287,6 +287,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { String apkamSymmetricKey, String defaultEncryptionPublicKey, RSAKeypair apkamKeyPair) async { + // Pkam auth will be retried until server approves/denies/expires the enrollment while (true) { logger.finer('Attempting pkam for $enrollmentIdFromServer'); bool pkamAuthResult = await _attemptPkamAuth( diff --git a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart index 0402f91a..187e4e0e 100644 --- a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart +++ b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart @@ -132,8 +132,7 @@ void main() { completer.complete(); }, count: 1, max: -1)); await completer.future; - // TODO - assert for atEnrollmentException - }, timeout: Timeout(Duration(minutes: 10))); + }, timeout: Timeout(Duration(minutes: 5))); } Future _notificationCallback( From acc292318b3ac1b7a7c636f0b2c530c464b0e113 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Fri, 15 Sep 2023 12:14:30 +0530 Subject: [PATCH 18/35] fix: changes for backward compatibility --- .../onboard/at_onboarding_service_impl.dart | 88 ++++++++++++------- .../src/util/at_onboarding_preference.dart | 2 + .../test/enrollment_test.dart | 2 + 3 files changed, 58 insertions(+), 34 deletions(-) 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 e3795caa..25bf925d 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 @@ -92,8 +92,9 @@ class AtOnboardingServiceImpl implements AtOnboardingService { @override Future onboard() async { - if (atOnboardingPreference.appName == null || - atOnboardingPreference.deviceName == null) { + if (atOnboardingPreference.enableEnrollmentDuringOnboard && + (atOnboardingPreference.appName == null || + atOnboardingPreference.deviceName == null)) { throw AtOnboardingException( 'appName and deviceName are mandatory for onboarding. Please set the params in AtOnboardingPreference'); } @@ -328,38 +329,23 @@ class AtOnboardingServiceImpl implements AtOnboardingService { Future _activateAtsign(AtLookupImpl atLookUpImpl) async { //1. Generate pkam key pair(if authMode is keyFile), encryption key pair, self encryption key and apkam symmetric key pair AtSecurityKeys atSecurityKeys = _generateKeyPairs(); - - var enrollBuilder = EnrollVerbBuilder() - ..appName = atOnboardingPreference.appName - ..deviceName = atOnboardingPreference.deviceName; - - // #TODO replace encryption util methods with at_chops methods when refactoring - enrollBuilder.encryptedDefaultEncryptedPrivateKey = - EncryptionUtil.encryptValue(atSecurityKeys.defaultEncryptionPrivateKey!, - atSecurityKeys.apkamSymmetricKey!); - enrollBuilder.encryptedDefaultSelfEncryptionKey = - EncryptionUtil.encryptValue(atSecurityKeys.defaultSelfEncryptionKey!, - atSecurityKeys.apkamSymmetricKey!); - enrollBuilder.apkamPublicKey = atSecurityKeys.apkamPublicKey; - - //2. Send enroll request to server - var enrollResult = await atLookUpImpl - .executeCommand(enrollBuilder.buildCommand(), auth: false); - if (enrollResult == null || enrollResult.isEmpty) { - throw AtOnboardingException('Enrollment response is null or empty'); - } else if (enrollResult.startsWith('error:')) { - throw AtOnboardingException('Enrollment error:$enrollResult'); + var enrollmentIdFromServer; + + //2. Send enrollment request to server if enable enrollment is set in preference + if (atOnboardingPreference.enableEnrollmentDuringOnboard) { + // server will update the apkam public key during enrollment.So don't have to manually update in this scenario. + enrollmentIdFromServer = + await _sendOnboardingEnrollment(atSecurityKeys, atLookUpImpl); + atSecurityKeys.enrollmentId = enrollmentIdFromServer; + } else { + // update pkam public key to server if enrollment is not set in preference + logger.finer('Updating PkamPublicKey to remote secondary'); + final pkamPublicKey = atSecurityKeys.apkamPublicKey; + String updateCommand = 'update:$AT_PKAM_PUBLIC_KEY $pkamPublicKey\n'; + String? pkamUpdateResult = + await atLookUpImpl.executeCommand(updateCommand, auth: false); + logger.info('PkamPublicKey update result: $pkamUpdateResult'); } - enrollResult = enrollResult.replaceFirst('data:', ''); - logger.finer('enrollResult: $enrollResult'); - var enrollResultJson = jsonDecode(enrollResult); - var enrollmentIdFromServer = enrollResultJson[enrollmentId]; - var enrollmentStatus = enrollResultJson['status']; - if (enrollmentStatus != 'approved') { - throw AtOnboardingException( - 'initial enrollment is not approved. Status from server: $enrollmentStatus'); - } - atSecurityKeys.enrollmentId = enrollmentIdFromServer; //3. Close connection to server try { @@ -371,7 +357,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { //4. initialise atClient and atChops and attempt a pkam auth to server. await _init(atSecurityKeys); - //4. create new connection to server and do pkam with enrollmentId + //5. create new connection to server and do pkam with enrollmentId try { _isPkamAuthenticated = await _atLookUp! .pkamAuthenticate(enrollmentId: enrollmentIdFromServer); @@ -409,6 +395,40 @@ class AtOnboardingServiceImpl implements AtOnboardingService { } } + Future _sendOnboardingEnrollment( + AtSecurityKeys atSecurityKeys, AtLookupImpl atLookUpImpl) async { + var enrollBuilder = EnrollVerbBuilder() + ..appName = atOnboardingPreference.appName + ..deviceName = atOnboardingPreference.deviceName; + + // #TODO replace encryption util methods with at_chops methods when refactoring + enrollBuilder.encryptedDefaultEncryptedPrivateKey = + EncryptionUtil.encryptValue(atSecurityKeys.defaultEncryptionPrivateKey!, + atSecurityKeys.apkamSymmetricKey!); + enrollBuilder.encryptedDefaultSelfEncryptionKey = + EncryptionUtil.encryptValue(atSecurityKeys.defaultSelfEncryptionKey!, + atSecurityKeys.apkamSymmetricKey!); + enrollBuilder.apkamPublicKey = atSecurityKeys.apkamPublicKey; + + var enrollResult = await atLookUpImpl + .executeCommand(enrollBuilder.buildCommand(), auth: false); + if (enrollResult == null || enrollResult.isEmpty) { + throw AtOnboardingException('Enrollment response is null or empty'); + } else if (enrollResult.startsWith('error:')) { + throw AtOnboardingException('Enrollment error:$enrollResult'); + } + enrollResult = enrollResult.replaceFirst('data:', ''); + logger.finer('enrollResult: $enrollResult'); + var enrollResultJson = jsonDecode(enrollResult); + var enrollmentIdFromServer = enrollResultJson[enrollmentId]; + var enrollmentStatus = enrollResultJson['status']; + if (enrollmentStatus != 'approved') { + throw AtOnboardingException( + 'initial enrollment is not approved. Status from server: $enrollmentStatus'); + } + return enrollmentIdFromServer; + } + Future _sendEnrollRequest( String appName, String deviceName, diff --git a/packages/at_onboarding_cli/lib/src/util/at_onboarding_preference.dart b/packages/at_onboarding_cli/lib/src/util/at_onboarding_preference.dart index db464c10..b395288d 100644 --- a/packages/at_onboarding_cli/lib/src/util/at_onboarding_preference.dart +++ b/packages/at_onboarding_cli/lib/src/util/at_onboarding_preference.dart @@ -33,4 +33,6 @@ class AtOnboardingPreference extends AtClientPreference { String? deviceName; int apkamAuthRetryDurationMins = 30; + + bool enableEnrollmentDuringOnboard = false; } diff --git a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart index 187e4e0e..e39b2a2e 100644 --- a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart +++ b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart @@ -25,6 +25,7 @@ void main() { String atSign = '@nareshđź› '; //1. Onboard first client AtOnboardingPreference preference_1 = getPreferenceForAuth(atSign); + preference_1..enableEnrollmentDuringOnboard = true; AtOnboardingService? onboardingService_1 = AtOnboardingServiceImpl(atSign, preference_1); bool status = await onboardingService_1.onboard(); @@ -91,6 +92,7 @@ void main() { String atSign = '@purnimađź› '; //1. Onboard first client AtOnboardingPreference preference_1 = getPreferenceForAuth(atSign); + preference_1.enableEnrollmentDuringOnboard = true; AtOnboardingService? onboardingService_1 = AtOnboardingServiceImpl(atSign, preference_1); bool status = await onboardingService_1.onboard(); From 6838c9e1d15e832b2c7655641bf57444603d6312 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Fri, 15 Sep 2023 15:08:51 +0530 Subject: [PATCH 19/35] fix: functional test issue fix and add error code change for apkam auth failed --- .../src/onboard/at_onboarding_service_impl.dart | 15 +++++++++------ .../test/enrollment_test.dart | 10 ++++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) 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 25bf925d..2c368d7d 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 @@ -312,11 +312,11 @@ class AtOnboardingServiceImpl implements AtOnboardingService { return true; } } on UnAuthenticatedException catch (e) { - if (e.message.contains('error:AT0401')) { + if (e.message.contains('error:AT0401') || + e.message.contains('error:AT0026')) { logger.finer('Retrying pkam auth'); await Future.delayed(retryInterval); } else if (e.message.contains('error:AT0025')) { - //# TODO change the error code once server bug is fixed logger.finer( 'enrollmentId $enrollmentIdFromServer denied.Exiting pkam retry logic'); throw AtEnrollmentException('enrollment denied'); @@ -329,7 +329,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { Future _activateAtsign(AtLookupImpl atLookUpImpl) async { //1. Generate pkam key pair(if authMode is keyFile), encryption key pair, self encryption key and apkam symmetric key pair AtSecurityKeys atSecurityKeys = _generateKeyPairs(); - var enrollmentIdFromServer; + String? enrollmentIdFromServer; //2. Send enrollment request to server if enable enrollment is set in preference if (atOnboardingPreference.enableEnrollmentDuringOnboard) { @@ -510,7 +510,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { ///write newly created encryption keypairs into atKeys file Future _generateAtKeysFile( - String currentEnrollmentId, AtSecurityKeys atSecurityKeys) async { + String? currentEnrollmentId, AtSecurityKeys atSecurityKeys) async { final atKeysMap = { AuthKeyType.pkamPublicKey: EncryptionUtil.encryptValue( atSecurityKeys.apkamPublicKey!, @@ -526,10 +526,13 @@ class AtOnboardingServiceImpl implements AtOnboardingService { ), AuthKeyType.selfEncryptionKey: atSecurityKeys.defaultSelfEncryptionKey!, _atSign: atSecurityKeys.defaultSelfEncryptionKey!, - AuthKeyType.apkamSymmetricKey: atSecurityKeys.apkamSymmetricKey!, - 'enrollmentId': currentEnrollmentId, + AuthKeyType.apkamSymmetricKey: atSecurityKeys.apkamSymmetricKey! }; + if (currentEnrollmentId != null) { + atKeysMap['enrollmentId'] = currentEnrollmentId; + } + if (atOnboardingPreference.authMode == PkamAuthMode.keysFile) { atKeysMap[AuthKeyType.pkamPrivateKey] = EncryptionUtil.encryptValue( atSecurityKeys.apkamPrivateKey!, diff --git a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart index e39b2a2e..ef84d7b0 100644 --- a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart +++ b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart @@ -82,6 +82,9 @@ void main() { expect(authResultWithEnrollment, true); enrolledClientKeysFile.deleteSync(); }, timeout: Timeout(Duration(minutes: 3))); + tearDown(() async { + await tearDownFunc(); + }); }); test( @@ -244,3 +247,10 @@ Future getAtKeys(String atSign) async { encryptionPrivateKey = EncryptionUtil.decryptValue( keysJSON['aesEncryptPrivateKey'], selfEncryptionKey); } + +Future tearDownFunc() async { + bool isExists = await Directory('storage/').exists(); + if (isExists) { + Directory('storage/').deleteSync(recursive: true); + } +} From 9a30de95fd815c76d7607514c57bf8e68124bdaf Mon Sep 17 00:00:00 2001 From: purnimavenkatasubbu Date: Fri, 15 Sep 2023 18:31:23 +0530 Subject: [PATCH 20/35] added tests --- .../test/enrollment_test.dart | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart index ef84d7b0..496ba7c5 100644 --- a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart +++ b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart @@ -7,6 +7,8 @@ import 'package:at_demo_data/at_demo_data.dart' as at_demos; import 'package:at_onboarding_cli/at_onboarding_cli.dart'; import 'package:at_utils/at_utils.dart'; import 'package:test/test.dart'; +import 'package:at_onboarding_cli/src/util/at_onboarding_exceptions.dart'; + var pkamPublicKey; var pkamPrivateKey; @@ -82,6 +84,55 @@ void main() { expect(authResultWithEnrollment, true); enrolledClientKeysFile.deleteSync(); }, timeout: Timeout(Duration(minutes: 3))); + + test( + 'A test to verify pkam authentication is successful when enableEnrollmentDuringOnboard flag is set to false', + () async { + // if onboard is testing use distinct demo atsign per test, + // since cram keys get deleted on server for already onboarded atsign + String atSign = '@ashishđź› '; + //1. Onboard first client + AtOnboardingPreference preference_1 = getPreferenceForAuth(atSign); + preference_1.enableEnrollmentDuringOnboard = false; + AtOnboardingService? onboardingService_1 = + AtOnboardingServiceImpl(atSign, preference_1); + bool status = await onboardingService_1.onboard(); + expect(status, true); + preference_1.privateKey = pkamPrivateKey; + + //2. authenticate first client + var authStatus = await onboardingService_1.authenticate(); + expect(authStatus, true); + }, timeout: Timeout(Duration(minutes: 10))); + + test( + 'A test to verify an onboarding exception is thrown when enableEnrollmentDuringOnboard is set to true and deviceName and appName are not passed', + () async { + // if onboard is testing use distinct demo atsign per test, + // since cram keys get deleted on server for already onboarded atsign + String atSign = '@colinđź› '; + // preference without appName and deviceName + AtOnboardingPreference preference_1 = AtOnboardingPreference() + ..rootDomain = 'vip.ve.atsign.zone' + ..isLocalStoreRequired = true + ..hiveStoragePath = 'storage/hive/client' + ..commitLogPath = 'storage/hive/client/commit' + ..cramSecret = at_demos.cramKeyMap[atSign] + ..namespace = + 'wavi' // unique identifier that can be used to identify data from your app + ..rootDomain = 'vip.ve.atsign.zone' + ..enableEnrollmentDuringOnboard = true; + + AtOnboardingService? onboardingService_1 = + AtOnboardingServiceImpl(atSign, preference_1); + expect( + () async => await onboardingService_1.onboard(), + throwsA(predicate((dynamic e) => + e is AtOnboardingException && + e.message == + 'appName and deviceName are mandatory for onboarding. Please set the params in AtOnboardingPreference'))); + }, timeout: Timeout(Duration(minutes: 10))); + tearDown(() async { await tearDownFunc(); }); From beaccd82c8a2eaf1a79154f6ee13b498c5357d56 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Mon, 9 Oct 2023 17:09:40 +0530 Subject: [PATCH 21/35] feat: replace onboard impl with at_auth.onboard --- .../at_onboarding_cli/example/onboard.dart | 9 ++-- .../onboard/at_onboarding_service_impl.dart | 47 +++++++------------ .../lib/src/onboard/at_security_keys.dart | 12 ----- packages/at_onboarding_cli/pubspec.yaml | 12 +++++ 4 files changed, 34 insertions(+), 46 deletions(-) delete mode 100644 packages/at_onboarding_cli/lib/src/onboard/at_security_keys.dart diff --git a/packages/at_onboarding_cli/example/onboard.dart b/packages/at_onboarding_cli/example/onboard.dart index 3c5fe939..dcec9787 100644 --- a/packages/at_onboarding_cli/example/onboard.dart +++ b/packages/at_onboarding_cli/example/onboard.dart @@ -2,17 +2,18 @@ import 'package:at_onboarding_cli/at_onboarding_cli.dart'; import 'package:at_utils/at_logger.dart'; Future main() async { - AtSignLogger.root_level = 'info'; - final atSign = '@alice'; + AtSignLogger.root_level = 'finest'; + final atSign = '@aliceđź› '; 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 = '/home/murali/.atsign/@aliceđź› _key.atKeys' ..appName = 'wavi' ..deviceName = 'pixel' - ..rootDomain = 'vip.ve.atsign.zone'; + ..rootDomain = 'vip.ve.atsign.zone' + ..enableEnrollmentDuringOnboard = true; AtOnboardingService? onboardingService = AtOnboardingServiceImpl(atSign, atOnboardingPreference); await onboardingService.onboard(); 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 2c368d7d..f9ca31c6 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 @@ -6,7 +6,7 @@ import 'dart:io'; import 'package:at_chops/at_chops.dart'; import 'package:at_client/at_client.dart'; -import 'package:at_onboarding_cli/src/onboard/at_security_keys.dart'; +import 'package:at_auth/at_auth.dart'; import 'package:at_onboarding_cli/src/util/at_onboarding_exceptions.dart'; import 'package:at_server_status/at_server_status.dart'; import 'package:at_utils/at_utils.dart'; @@ -118,35 +118,22 @@ class AtOnboardingServiceImpl implements AtOnboardingService { // check and wait till secondary exists await _waitUntilSecondaryCreated(atLookUpImpl); - if (await isOnboarded()) { - throw AtActivateException('atsign is already activated'); - } - - try { - // authenticate into secondary using cram secret - _isAtsignOnboarded = (await atLookUpImpl - .authenticate_cram(atOnboardingPreference.cramSecret)); - - if (_isAtsignOnboarded) { - logger.info('Cram authentication successful'); - await _activateAtsign(atLookUpImpl); - } else { - throw AtActivateException( - 'Cram authentication failed. Please check the cram key' - ' and try again \n(or) contact support@atsign.com'); - } - } on Exception catch (e) { - if (e.toString().contains('Auth failed')) { - throw AtActivateException( - 'Cram authentication failed. Please check the cram key' - ' and try again \n(or) contact support@atsign.com'); - } - logger.severe('Caught exception: $e'); - } on Error catch (e, trace) { - logger.severe('Caught error: $e $trace'); - } finally { - await atLookUpImpl.close(); - } + // if (await isOnboarded()) { + // throw AtActivateException('atsign is already activated'); + // } + + var atAuthService = AtAuthImpl(); + var atOnboardingRequest = AtOnboardingRequest(_atSign); + atOnboardingRequest.rootDomain = atOnboardingPreference.rootDomain; + atOnboardingRequest.rootPort = atOnboardingPreference.rootPort; + atOnboardingRequest.enableEnrollment = + atOnboardingPreference.enableEnrollmentDuringOnboard; + atOnboardingRequest.appName = atOnboardingPreference.appName; + atOnboardingRequest.deviceName = atOnboardingPreference.deviceName; + atOnboardingRequest.publicKeyId = atOnboardingPreference.publicKeyId; + var atOnboardingResponse = await atAuthService.onboard( + atOnboardingRequest, atOnboardingPreference.cramSecret!); + print('****atOnboardingResponse: $atOnboardingResponse'); return _isAtsignOnboarded; } diff --git a/packages/at_onboarding_cli/lib/src/onboard/at_security_keys.dart b/packages/at_onboarding_cli/lib/src/onboard/at_security_keys.dart deleted file mode 100644 index e307a66c..00000000 --- a/packages/at_onboarding_cli/lib/src/onboard/at_security_keys.dart +++ /dev/null @@ -1,12 +0,0 @@ -/// Holder for different encryption keys that will be stored in .atKeys file. -/// Apkam symmetric key, enrollmentId and defaultSelfEncryptionKey will be stored in unencrypted format in .atKeys file. -/// All other values will be encrypted before saving to .atKeys file. -class AtSecurityKeys { - String? apkamPublicKey; - String? apkamPrivateKey; - String? defaultEncryptionPublicKey; - String? defaultEncryptionPrivateKey; - String? defaultSelfEncryptionKey; - String? apkamSymmetricKey; - String? enrollmentId; -} diff --git a/packages/at_onboarding_cli/pubspec.yaml b/packages/at_onboarding_cli/pubspec.yaml index 15d147d3..4041c51a 100644 --- a/packages/at_onboarding_cli/pubspec.yaml +++ b/packages/at_onboarding_cli/pubspec.yaml @@ -27,6 +27,18 @@ dependencies: http: ^0.13.6 at_chops: ^1.0.4 +dependency_overrides: + at_auth: + git: + url: https://github.com/atsign-foundation/at_libraries + path: packages/at_auth + ref: at_auth_new_package + at_chops: + git: + url: https://github.com/atsign-foundation/at_libraries + path: packages/at_chops + ref: at_auth_new_package + dev_dependencies: lints: ^2.1.0 test: ^1.24.2 From def786a1d8af2f7f036fa8e6d4fb45f3cd5c156b Mon Sep 17 00:00:00 2001 From: murali-shris Date: Tue, 10 Oct 2023 14:00:32 +0530 Subject: [PATCH 22/35] fix: generate keys file after onboard --- .../lib/src/onboard/at_onboarding_service_impl.dart | 6 ++++++ 1 file changed, 6 insertions(+) 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 f9ca31c6..68eded9c 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 @@ -134,6 +134,12 @@ class AtOnboardingServiceImpl implements AtOnboardingService { var atOnboardingResponse = await atAuthService.onboard( atOnboardingRequest, atOnboardingPreference.cramSecret!); print('****atOnboardingResponse: $atOnboardingResponse'); + if (atOnboardingResponse.isSuccessful) { + logger.finer( + 'Onboarding successful.Generating keyfile in path: ${atOnboardingPreference.atKeysFilePath}'); + await _generateAtKeysFile(atOnboardingResponse.enrollmentId, + atOnboardingResponse.atSecurityKeys!); + } return _isAtsignOnboarded; } From 23f5e20c628a73cff15dbc4f09bb36ae2461c98a Mon Sep 17 00:00:00 2001 From: murali-shris Date: Tue, 10 Oct 2023 18:18:03 +0530 Subject: [PATCH 23/35] fix: auth in onboarding service replaced with new package at_auth --- .../example/apkam_authenticate.dart | 26 +- .../onboard/at_onboarding_service_impl.dart | 283 ++++-------------- 2 files changed, 71 insertions(+), 238 deletions(-) diff --git a/packages/at_onboarding_cli/example/apkam_authenticate.dart b/packages/at_onboarding_cli/example/apkam_authenticate.dart index 733643da..445ea65e 100644 --- a/packages/at_onboarding_cli/example/apkam_authenticate.dart +++ b/packages/at_onboarding_cli/example/apkam_authenticate.dart @@ -1,23 +1,23 @@ import 'package:at_client/at_client.dart'; import 'package:at_lookup/at_lookup.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'; - final atSign = '@alice'; + // final enrollIdFromServer = '867307c7-53bd-4736-8fe7-1520de58ce78'; + AtSignLogger.root_level = 'finest'; + final atSign = '@aliceđź› '; AtOnboardingPreference atOnboardingPreference = AtOnboardingPreference() ..namespace = - 'buzz' // unique identifier that can be used to identify data from your app - ..atKeysFilePath = '/home/user/atsign/alice_buzzkey.atKeys' + 'wavi' // unique identifier that can be used to identify data from your app + ..atKeysFilePath = '/home/murali/.atsign/@aliceđź› _key.atKeys' ..rootDomain = 'vip.ve.atsign.zone'; AtOnboardingService? onboardingService = AtOnboardingServiceImpl( - atSign, atOnboardingPreference, - enrollmentId: enrollIdFromServer); - await onboardingService.authenticate( - enrollmentId: enrollIdFromServer); // when authenticating - AtLookUp? atLookup = onboardingService.atLookUp; - AtClient? client = onboardingService.atClient; - print(await client?.getKeys()); - print(await atLookup?.scan(regex: 'publickey')); - await onboardingService.close(); + atSign, atOnboardingPreference); + 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(); } 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 68eded9c..2f40d4eb 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 @@ -26,7 +26,6 @@ import '../util/onboarding_util.dart'; ///class containing service that can onboard/activate/authenticate @signs class AtOnboardingServiceImpl implements AtOnboardingService { late final String _atSign; - bool _isPkamAuthenticated = false; bool _isAtsignOnboarded = false; AtSignLogger logger = AtSignLogger('OnboardingCli'); AtOnboardingPreference atOnboardingPreference; @@ -76,8 +75,8 @@ class AtOnboardingServiceImpl implements AtOnboardingService { _atClient ??= atClientManager.atClient; } - Future _init(AtSecurityKeys atKeysFile, {String? enrollmentId}) async { - atChops ??= _createAtChops(atKeysFile); + Future _init(AtAuthKeys atAuthKeys, {String? enrollmentId}) async { + atChops ??= _createAtChops(atAuthKeys); await _initAtClient(atChops!, enrollmentId: enrollmentId); _atLookUp!.atChops = atChops; _atClient!.atChops = atChops; @@ -118,11 +117,11 @@ class AtOnboardingServiceImpl implements AtOnboardingService { // check and wait till secondary exists await _waitUntilSecondaryCreated(atLookUpImpl); - // if (await isOnboarded()) { - // throw AtActivateException('atsign is already activated'); - // } + if (await isOnboarded()) { + throw AtActivateException('atsign is already activated'); + } - var atAuthService = AtAuthImpl(); + var atAuthImpl = AtAuthImpl(); var atOnboardingRequest = AtOnboardingRequest(_atSign); atOnboardingRequest.rootDomain = atOnboardingPreference.rootDomain; atOnboardingRequest.rootPort = atOnboardingPreference.rootPort; @@ -131,14 +130,14 @@ class AtOnboardingServiceImpl implements AtOnboardingService { atOnboardingRequest.appName = atOnboardingPreference.appName; atOnboardingRequest.deviceName = atOnboardingPreference.deviceName; atOnboardingRequest.publicKeyId = atOnboardingPreference.publicKeyId; - var atOnboardingResponse = await atAuthService.onboard( + var atOnboardingResponse = await atAuthImpl.onboard( atOnboardingRequest, atOnboardingPreference.cramSecret!); - print('****atOnboardingResponse: $atOnboardingResponse'); + logger.finer('Onboarding Response: $atOnboardingResponse'); if (atOnboardingResponse.isSuccessful) { logger.finer( 'Onboarding successful.Generating keyfile in path: ${atOnboardingPreference.atKeysFilePath}'); - await _generateAtKeysFile(atOnboardingResponse.enrollmentId, - atOnboardingResponse.atSecurityKeys!); + await _generateAtKeysFile( + atOnboardingResponse.enrollmentId, atOnboardingResponse.atAuthKeys!); } return _isAtsignOnboarded; } @@ -217,7 +216,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { enrollmentIdFromServer, atLookUpImpl), apkamSymmetricKey); - var atSecurityKeys = AtSecurityKeys() + var atAuthKeys = AtAuthKeys() ..defaultEncryptionPrivateKey = decryptedEncryptionPrivateKey ..defaultEncryptionPublicKey = defaultEncryptionPublicKey ..apkamSymmetricKey = apkamSymmetricKey @@ -225,14 +224,14 @@ class AtOnboardingServiceImpl implements AtOnboardingService { ..apkamPublicKey = apkamKeyPair.publicKey.toString() ..apkamPrivateKey = apkamKeyPair.privateKey.toString(); logger.finer('Generating keys file for $enrollmentIdFromServer'); - await _generateAtKeysFile(enrollmentIdFromServer, atSecurityKeys); + await _generateAtKeysFile(enrollmentIdFromServer, atAuthKeys); }); } Future _getEncryptionPrivateKeyFromServer( String enrollmentIdFromServer, AtLookUp atLookUp) async { var privateKeyCommand = - 'keys:get:keyName:$enrollmentIdFromServer.$defaultEncryptionPrivateKey.__manage$_atSign\n'; + 'keys:get:keyName:$enrollmentIdFromServer.${AtConstants.defaultEncryptionPrivateKey}.__manage$_atSign\n'; String encryptionPrivateKeyFromServer; try { var getPrivateKeyResult = @@ -253,7 +252,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { Future _getSelfEncryptionKeyFromServer( String enrollmentIdFromServer, AtLookUp atLookUp) async { var selfEncryptionKeyCommand = - 'keys:get:keyName:$enrollmentIdFromServer.$defaultSelfEncryptionKey.__manage$_atSign\n'; + 'keys:get:keyName:$enrollmentIdFromServer.${AtConstants.defaultSelfEncryptionKey}.__manage$_atSign\n'; String selfEncryptionKeyFromServer; try { var getSelfEncryptionKeyResult = @@ -318,110 +317,6 @@ class AtOnboardingServiceImpl implements AtOnboardingService { return false; } - ///method to generate/update encryption key-pairs to activate an atsign - Future _activateAtsign(AtLookupImpl atLookUpImpl) async { - //1. Generate pkam key pair(if authMode is keyFile), encryption key pair, self encryption key and apkam symmetric key pair - AtSecurityKeys atSecurityKeys = _generateKeyPairs(); - String? enrollmentIdFromServer; - - //2. Send enrollment request to server if enable enrollment is set in preference - if (atOnboardingPreference.enableEnrollmentDuringOnboard) { - // server will update the apkam public key during enrollment.So don't have to manually update in this scenario. - enrollmentIdFromServer = - await _sendOnboardingEnrollment(atSecurityKeys, atLookUpImpl); - atSecurityKeys.enrollmentId = enrollmentIdFromServer; - } else { - // update pkam public key to server if enrollment is not set in preference - logger.finer('Updating PkamPublicKey to remote secondary'); - final pkamPublicKey = atSecurityKeys.apkamPublicKey; - String updateCommand = 'update:$AT_PKAM_PUBLIC_KEY $pkamPublicKey\n'; - String? pkamUpdateResult = - await atLookUpImpl.executeCommand(updateCommand, auth: false); - logger.info('PkamPublicKey update result: $pkamUpdateResult'); - } - - //3. Close connection to server - try { - atLookUpImpl.close(); - } on Exception catch (e) { - logger.severe('error while closing connection to server: $e'); - } - - //4. initialise atClient and atChops and attempt a pkam auth to server. - await _init(atSecurityKeys); - - //5. create new connection to server and do pkam with enrollmentId - try { - _isPkamAuthenticated = await _atLookUp! - .pkamAuthenticate(enrollmentId: enrollmentIdFromServer); - } on UnAuthenticatedException { - throw AtOnboardingException( - 'Pkam auth with enrollmentId-$enrollmentIdFromServer failed'); - } - if (!_isPkamAuthenticated) { - throw AtOnboardingException( - 'Pkam auth with enrollmentId-$enrollmentIdFromServer failed'); - } - //5. If Pkam auth is success, update encryption public key to secondary and delete cram key from server - if (_isPkamAuthenticated) { - final encryptionPublicKey = atSecurityKeys.defaultEncryptionPublicKey; - UpdateVerbBuilder updateBuilder = UpdateVerbBuilder() - ..atKey = 'publickey' - ..isPublic = true - ..value = encryptionPublicKey - ..sharedBy = _atSign; - String? encryptKeyUpdateResult = - await _atLookUp!.executeVerb(updateBuilder); - logger - .info('Encryption public key update result $encryptKeyUpdateResult'); - // deleting cram secret from the keystore as cram auth is complete - DeleteVerbBuilder deleteBuilder = DeleteVerbBuilder() - ..atKey = AT_CRAM_SECRET; - String? deleteResponse = await _atLookUp!.executeVerb(deleteBuilder); - logger.info('Cram secret delete response : $deleteResponse'); - //displays status of the atsign - logger.finer(await getServerStatus()); - stdout.writeln('[Success]----------atSign activated---------'); - stdout.writeln('-----------------saving atkeys file---------'); - await _generateAtKeysFile(enrollmentIdFromServer, atSecurityKeys); - await _persistKeysLocalSecondary(); - } - } - - Future _sendOnboardingEnrollment( - AtSecurityKeys atSecurityKeys, AtLookupImpl atLookUpImpl) async { - var enrollBuilder = EnrollVerbBuilder() - ..appName = atOnboardingPreference.appName - ..deviceName = atOnboardingPreference.deviceName; - - // #TODO replace encryption util methods with at_chops methods when refactoring - enrollBuilder.encryptedDefaultEncryptedPrivateKey = - EncryptionUtil.encryptValue(atSecurityKeys.defaultEncryptionPrivateKey!, - atSecurityKeys.apkamSymmetricKey!); - enrollBuilder.encryptedDefaultSelfEncryptionKey = - EncryptionUtil.encryptValue(atSecurityKeys.defaultSelfEncryptionKey!, - atSecurityKeys.apkamSymmetricKey!); - enrollBuilder.apkamPublicKey = atSecurityKeys.apkamPublicKey; - - var enrollResult = await atLookUpImpl - .executeCommand(enrollBuilder.buildCommand(), auth: false); - if (enrollResult == null || enrollResult.isEmpty) { - throw AtOnboardingException('Enrollment response is null or empty'); - } else if (enrollResult.startsWith('error:')) { - throw AtOnboardingException('Enrollment error:$enrollResult'); - } - enrollResult = enrollResult.replaceFirst('data:', ''); - logger.finer('enrollResult: $enrollResult'); - var enrollResultJson = jsonDecode(enrollResult); - var enrollmentIdFromServer = enrollResultJson[enrollmentId]; - var enrollmentStatus = enrollResultJson['status']; - if (enrollmentStatus != 'approved') { - throw AtOnboardingException( - 'initial enrollment is not approved. Status from server: $enrollmentStatus'); - } - return enrollmentIdFromServer; - } - Future _sendEnrollRequest( String appName, String deviceName, @@ -447,79 +342,31 @@ class AtOnboardingServiceImpl implements AtOnboardingService { } enrollResult = enrollResult.replaceFirst('data:', ''); var enrollJson = jsonDecode(enrollResult); - var enrollmentIdFromServer = enrollJson[enrollmentId]; + var enrollmentIdFromServer = enrollJson[AtConstants.enrollmentId]; logger.finer('enrollmentIdFromServer: $enrollmentIdFromServer'); return EnrollResponse(enrollmentIdFromServer, getEnrollStatusFromString(enrollJson['status'])); } - AtSecurityKeys _generateKeyPairs() { - // generate user encryption keypair - logger.info('Generating encryption keypair'); - var encryptionKeyPair = generateRsaKeypair(); - - //generate selfEncryptionKey - var selfEncryptionKey = generateAESKey(); - var apkamSymmetricKey = generateAESKey(); - var atKeysFile = AtSecurityKeys(); - stdout.writeln( - '[Information] Generating your encryption keys and .atKeys file\n'); - late String apkamPublicKey; - //generating pkamKeyPair only if authMode is keysFile - if (atOnboardingPreference.authMode == PkamAuthMode.keysFile) { - logger.info('Generating pkam keypair'); - var apkamRsaKeypair = generateRsaKeypair(); - atKeysFile.apkamPublicKey = apkamRsaKeypair.publicKey.toString(); - atKeysFile.apkamPrivateKey = apkamRsaKeypair.privateKey.toString(); - apkamPublicKey = apkamRsaKeypair.publicKey.toString(); - } else if (atOnboardingPreference.authMode == PkamAuthMode.sim) { - // get the public key from secure element - apkamPublicKey = - atChops!.readPublicKey(atOnboardingPreference.publicKeyId!); - logger.info('pkam public key from sim: $apkamPublicKey'); - - // encryption key pair and self encryption symmetric key - // are not available to injected at_chops. Set it here - atChops!.atChopsKeys.atEncryptionKeyPair = AtEncryptionKeyPair.create( - encryptionKeyPair.publicKey.toString(), - encryptionKeyPair.privateKey.toString()); - atChops!.atChopsKeys.selfEncryptionKey = AESKey(selfEncryptionKey); - atChops!.atChopsKeys.apkamSymmetricKey = AESKey(apkamSymmetricKey); - } - atKeysFile.apkamPublicKey = apkamPublicKey; - //Standard order of an atKeys file is -> - // pkam keypair -> encryption keypair -> selfEncryption key -> enrollmentId --> apkam symmetric key --> - // @sign: selfEncryptionKey[self encryption key again] - // note: "->" stands for "followed by" - atKeysFile.defaultEncryptionPublicKey = - encryptionKeyPair.publicKey.toString(); - atKeysFile.defaultEncryptionPrivateKey = - encryptionKeyPair.privateKey.toString(); - atKeysFile.defaultSelfEncryptionKey = selfEncryptionKey; - atKeysFile.apkamSymmetricKey = apkamSymmetricKey; - - return atKeysFile; - } - ///write newly created encryption keypairs into atKeys file Future _generateAtKeysFile( - String? currentEnrollmentId, AtSecurityKeys atSecurityKeys) async { + String? currentEnrollmentId, AtAuthKeys atAuthKeys) async { final atKeysMap = { AuthKeyType.pkamPublicKey: EncryptionUtil.encryptValue( - atSecurityKeys.apkamPublicKey!, - atSecurityKeys.defaultSelfEncryptionKey!, + atAuthKeys.apkamPublicKey!, + atAuthKeys.defaultSelfEncryptionKey!, ), AuthKeyType.encryptionPublicKey: EncryptionUtil.encryptValue( - atSecurityKeys.defaultEncryptionPublicKey!, - atSecurityKeys.defaultSelfEncryptionKey!, + atAuthKeys.defaultEncryptionPublicKey!, + atAuthKeys.defaultSelfEncryptionKey!, ), AuthKeyType.encryptionPrivateKey: EncryptionUtil.encryptValue( - atSecurityKeys.defaultEncryptionPrivateKey!, - atSecurityKeys.defaultSelfEncryptionKey!, + atAuthKeys.defaultEncryptionPrivateKey!, + atAuthKeys.defaultSelfEncryptionKey!, ), - AuthKeyType.selfEncryptionKey: atSecurityKeys.defaultSelfEncryptionKey!, - _atSign: atSecurityKeys.defaultSelfEncryptionKey!, - AuthKeyType.apkamSymmetricKey: atSecurityKeys.apkamSymmetricKey! + AuthKeyType.selfEncryptionKey: atAuthKeys.defaultSelfEncryptionKey!, + _atSign: atAuthKeys.defaultSelfEncryptionKey!, + AuthKeyType.apkamSymmetricKey: atAuthKeys.apkamSymmetricKey! }; if (currentEnrollmentId != null) { @@ -528,8 +375,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { if (atOnboardingPreference.authMode == PkamAuthMode.keysFile) { atKeysMap[AuthKeyType.pkamPrivateKey] = EncryptionUtil.encryptValue( - atSecurityKeys.apkamPrivateKey!, - atSecurityKeys.defaultSelfEncryptionKey!); + atAuthKeys.apkamPrivateKey!, atAuthKeys.defaultSelfEncryptionKey!); } if (!atOnboardingPreference.atKeysFilePath!.endsWith('.atKeys')) { @@ -553,74 +399,61 @@ class AtOnboardingServiceImpl implements AtOnboardingService { } ///back-up encryption keys to local secondary + /// #TODO remove this method in future when all keys are read from AtChops Future _persistKeysLocalSecondary() async { //when authenticating keys need to be fetched from atKeys file - AtSecurityKeys atSecurityKeys = _decryptAtKeysFile( + AtAuthKeys atAuthKeys = _decryptAtKeysFile( (await _readAtKeysFile(atOnboardingPreference.atKeysFilePath))); //backup keys into local secondary bool? response = await _atClient ?.getLocalSecondary() - ?.putValue(AT_PKAM_PUBLIC_KEY, atSecurityKeys.apkamPublicKey!); + ?.putValue(AtConstants.atPkamPublicKey, atAuthKeys.apkamPublicKey!); logger.finer('PkamPublicKey persist to localSecondary: status $response'); // save pkam private key only when auth mode is keyFile. if auth mode is sim/any other secure element private key cannot be read and hence will not be part of keys file if (atOnboardingPreference.authMode == PkamAuthMode.keysFile) { response = await _atClient ?.getLocalSecondary() - ?.putValue(AT_PKAM_PRIVATE_KEY, atSecurityKeys.apkamPrivateKey!); + ?.putValue(AtConstants.atPkamPrivateKey, atAuthKeys.apkamPrivateKey!); logger .finer('PkamPrivateKey persist to localSecondary: status $response'); } response = await _atClient?.getLocalSecondary()?.putValue( - '$AT_ENCRYPTION_PUBLIC_KEY$_atSign', - atSecurityKeys.defaultEncryptionPublicKey!); + '${AtConstants.atEncryptionPublicKey}$_atSign', + atAuthKeys.defaultEncryptionPublicKey!); logger.finer( 'EncryptionPublicKey persist to localSecondary: status $response'); response = await _atClient?.getLocalSecondary()?.putValue( - AT_ENCRYPTION_PRIVATE_KEY, atSecurityKeys.defaultEncryptionPrivateKey!); + AtConstants.atEncryptionPrivateKey, + atAuthKeys.defaultEncryptionPrivateKey!); logger.finer( 'EncryptionPrivateKey persist to localSecondary: status $response'); response = await _atClient?.getLocalSecondary()?.putValue( - AT_ENCRYPTION_SELF_KEY, atSecurityKeys.defaultSelfEncryptionKey!); + AtConstants.atEncryptionSelfKey, atAuthKeys.defaultSelfEncryptionKey!); } @override Future authenticate({String? enrollmentId}) async { - // decrypts all the keys in .atKeysFile using the SelfEncryptionKey - // and stores the keys in a map - var atSecurityKeys = _decryptAtKeysFile( - await _readAtKeysFile(atOnboardingPreference.atKeysFilePath)); - var pkamPrivateKey = atSecurityKeys.apkamPrivateKey; - - if (atOnboardingPreference.authMode == PkamAuthMode.keysFile && - pkamPrivateKey == null) { - throw AtPrivateKeyNotFoundException( - 'Unable to read PkamPrivateKey from provided atKeys file at path: ' - '${atOnboardingPreference.atKeysFilePath}. Please provide a valid atKeys file', - exceptionScenario: ExceptionScenario.invalidValueProvided); - } - await _init(atSecurityKeys, enrollmentId: enrollmentId); - logger.finer('Authenticating using PKAM'); - try { - _isPkamAuthenticated = - (await _atLookUp?.pkamAuthenticate(enrollmentId: enrollmentId))!; - } on Exception catch (e) { - logger.severe('Caught exception: $e'); - throw UnAuthenticatedException('Unable to authenticate'); - } - logger.finer( - 'PKAM auth result: ${_isPkamAuthenticated ? 'success' : 'failed'}'); - + var atAuthImpl = AtAuthImpl(); + var atAuthRequest = AtAuthRequest(_atSign, + atOnboardingPreference.rootDomain, atOnboardingPreference.rootPort) + ..enrollmentId = enrollmentId + ..atKeysFilePath = atOnboardingPreference.atKeysFilePath + ..authMode = atOnboardingPreference.authMode; + var atAuthResponse = await atAuthImpl.authenticate(atAuthRequest); + logger.finer('Auth response: $atAuthResponse'); if (!_isAtsignOnboarded && - _isPkamAuthenticated && + atAuthResponse.isSuccessful && atOnboardingPreference.atKeysFilePath != null) { logger.finer('Calling persist keys to local secondary'); + await _initAtClient(atAuthImpl.atChops!, + enrollmentId: atAuthResponse.enrollmentId); await _persistKeysLocalSecondary(); } - return _isPkamAuthenticated; + return atAuthResponse.isSuccessful; } - AtChops _createAtChops(AtSecurityKeys atKeysFile) { + AtChops _createAtChops(AtAuthKeys atKeysFile) { final atEncryptionKeyPair = AtEncryptionKeyPair.create( atKeysFile.defaultEncryptionPublicKey!, atKeysFile.defaultEncryptionPrivateKey!); @@ -656,25 +489,25 @@ class AtOnboardingServiceImpl implements AtOnboardingService { return jsonData![AuthKeyType.selfEncryptionKey]!; } - AtSecurityKeys _decryptAtKeysFile(Map jsonData) { - var securityKeys = AtSecurityKeys(); + AtAuthKeys _decryptAtKeysFile(Map jsonData) { + var atAuthKeys = AtAuthKeys(); String decryptionKey = _getDecryptionKey(jsonData); - securityKeys.defaultEncryptionPublicKey = EncryptionUtil.decryptValue( + atAuthKeys.defaultEncryptionPublicKey = EncryptionUtil.decryptValue( jsonData[AuthKeyType.encryptionPublicKey]!, decryptionKey); - securityKeys.defaultEncryptionPrivateKey = EncryptionUtil.decryptValue( + atAuthKeys.defaultEncryptionPrivateKey = EncryptionUtil.decryptValue( jsonData[AuthKeyType.encryptionPrivateKey]!, decryptionKey); - securityKeys.defaultSelfEncryptionKey = decryptionKey; - securityKeys.apkamPublicKey = EncryptionUtil.decryptValue( + atAuthKeys.defaultSelfEncryptionKey = decryptionKey; + atAuthKeys.apkamPublicKey = EncryptionUtil.decryptValue( jsonData[AuthKeyType.pkamPublicKey]!, decryptionKey); // pkam private key will not be saved in keyfile if auth mode is sim/any other secure element. // decrypt the private key only when auth mode is keysFile if (atOnboardingPreference.authMode == PkamAuthMode.keysFile) { - securityKeys.apkamPrivateKey = EncryptionUtil.decryptValue( + atAuthKeys.apkamPrivateKey = EncryptionUtil.decryptValue( jsonData[AuthKeyType.pkamPrivateKey]!, decryptionKey); } - securityKeys.apkamSymmetricKey = jsonData[AuthKeyType.apkamSymmetricKey]; - securityKeys.enrollmentId = jsonData[enrollmentId]; - return securityKeys; + atAuthKeys.apkamSymmetricKey = jsonData[AuthKeyType.apkamSymmetricKey]; + atAuthKeys.enrollmentId = jsonData[AtConstants.enrollmentId]; + return atAuthKeys; } Future _retrieveEncryptionPublicKey(AtLookUp atLookupImpl) async { From 0d6ce72191e89d603f192e92ab1e9a4e6119a0ac Mon Sep 17 00:00:00 2001 From: murali-shris Date: Thu, 12 Oct 2023 15:24:54 +0530 Subject: [PATCH 24/35] fix: modify unit tests --- .../src/onboard/at_onboarding_service.dart | 5 ++ .../onboard/at_onboarding_service_impl.dart | 39 ++++--------- .../test/at_onboarding_cli_test.dart | 56 +++++++------------ 3 files changed, 36 insertions(+), 64 deletions(-) diff --git a/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart b/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart index adf25d86..36df34ee 100644 --- a/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart +++ b/packages/at_onboarding_cli/lib/src/onboard/at_onboarding_service.dart @@ -1,6 +1,7 @@ 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 { @@ -49,4 +50,8 @@ abstract class AtOnboardingService { set atChops(AtChops? atChops); AtChops? get atChops; + + set atAuth(AtAuth? atAuth); + + AtAuth? get atAuth; } 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 2f40d4eb..63ae2044 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 @@ -75,14 +75,6 @@ class AtOnboardingServiceImpl implements AtOnboardingService { _atClient ??= atClientManager.atClient; } - Future _init(AtAuthKeys atAuthKeys, {String? enrollmentId}) async { - atChops ??= _createAtChops(atAuthKeys); - await _initAtClient(atChops!, enrollmentId: enrollmentId); - _atLookUp!.atChops = atChops; - _atClient!.atChops = atChops; - _atClient!.getPreferences()!.useAtChops = true; - } - @override @Deprecated('Use getter') Future getAtClient() async { @@ -121,7 +113,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { throw AtActivateException('atsign is already activated'); } - var atAuthImpl = AtAuthImpl(); + atAuth ??= AtAuthImpl(); var atOnboardingRequest = AtOnboardingRequest(_atSign); atOnboardingRequest.rootDomain = atOnboardingPreference.rootDomain; atOnboardingRequest.rootPort = atOnboardingPreference.rootPort; @@ -130,8 +122,8 @@ class AtOnboardingServiceImpl implements AtOnboardingService { atOnboardingRequest.appName = atOnboardingPreference.appName; atOnboardingRequest.deviceName = atOnboardingPreference.deviceName; atOnboardingRequest.publicKeyId = atOnboardingPreference.publicKeyId; - var atOnboardingResponse = await atAuthImpl.onboard( - atOnboardingRequest, atOnboardingPreference.cramSecret!); + var atOnboardingResponse = await atAuth! + .onboard(atOnboardingRequest, atOnboardingPreference.cramSecret!); logger.finer('Onboarding Response: $atOnboardingResponse'); if (atOnboardingResponse.isSuccessful) { logger.finer( @@ -433,19 +425,19 @@ class AtOnboardingServiceImpl implements AtOnboardingService { @override Future authenticate({String? enrollmentId}) async { - var atAuthImpl = AtAuthImpl(); + atAuth ??= AtAuthImpl(); var atAuthRequest = AtAuthRequest(_atSign, atOnboardingPreference.rootDomain, atOnboardingPreference.rootPort) ..enrollmentId = enrollmentId ..atKeysFilePath = atOnboardingPreference.atKeysFilePath ..authMode = atOnboardingPreference.authMode; - var atAuthResponse = await atAuthImpl.authenticate(atAuthRequest); + var atAuthResponse = await atAuth!.authenticate(atAuthRequest); logger.finer('Auth response: $atAuthResponse'); if (!_isAtsignOnboarded && atAuthResponse.isSuccessful && atOnboardingPreference.atKeysFilePath != null) { logger.finer('Calling persist keys to local secondary'); - await _initAtClient(atAuthImpl.atChops!, + await _initAtClient(atAuth!.atChops!, enrollmentId: atAuthResponse.enrollmentId); await _persistKeysLocalSecondary(); } @@ -453,21 +445,6 @@ class AtOnboardingServiceImpl implements AtOnboardingService { return atAuthResponse.isSuccessful; } - 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); - } - ///method to read and return data from .atKeysFile ///returns map containing encryption keys Future> _readAtKeysFile(String? atKeysFilePath) async { @@ -668,5 +645,9 @@ class AtOnboardingServiceImpl implements AtOnboardingService { AtLookUp? get atLookUp => _atLookUp; @override + @Deprecated('AtChops will be created in AtAuth') AtChops? atChops; + + @override + AtAuth? atAuth; } diff --git a/packages/at_onboarding_cli/test/at_onboarding_cli_test.dart b/packages/at_onboarding_cli/test/at_onboarding_cli_test.dart index 89d550d2..b06c7b00 100644 --- a/packages/at_onboarding_cli/test/at_onboarding_cli_test.dart +++ b/packages/at_onboarding_cli/test/at_onboarding_cli_test.dart @@ -1,65 +1,53 @@ import 'dart:io'; +import 'package:at_chops/at_chops.dart'; import 'package:at_onboarding_cli/at_onboarding_cli.dart'; import 'package:at_utils/at_logger.dart'; import 'package:mocktail/mocktail.dart'; import 'package:at_lookup/at_lookup.dart'; import 'package:at_client/at_client.dart'; +import 'package:at_auth/at_auth.dart'; import 'package:test/expect.dart'; import 'package:test/scaffolding.dart'; import 'package:at_demo_data/at_demo_data.dart' as at_demo; +class MockAtLookupImpl extends Mock implements AtLookupImpl {} + +class MockAtAuthImpl extends Mock implements AtAuthImpl {} + +class FakeAtAuthRequest extends Fake implements AtAuthRequest {} + void main() { AtLookupImpl mockAtLookup = MockAtLookupImpl(); + AtAuthImpl mockAtAuth = MockAtAuthImpl(); setUp(() { reset(mockAtLookup); + reset(mockAtAuth); + registerFallbackValue(FakeAtAuthRequest()); }); group('A group of tests to verify at_chops creation in onboarding_cli', () { AtSignLogger.root_level = 'FINER'; - test('A test to whether at_chops instance is set on authenticate', - () async { + test('A test to check authenticate true', () async { final atSign = '@aliceđź› '; AtOnboardingPreference onboardingPreference = AtOnboardingPreference() ..atKeysFilePath = 'test/data/@aliceđź› .atKeys'; AtOnboardingService onboardingService = AtOnboardingServiceImpl(atSign, onboardingPreference); onboardingService.atLookUp = mockAtLookup; + mockAtAuth.atChops = AtChopsImpl(AtChopsKeys()); + onboardingService.atAuth = mockAtAuth; onboardingService.atClient = await AtClientImpl.create(atSign, '.wavi', getAlicePreference()); when(() => mockAtLookup.pkamAuthenticate()) .thenAnswer((_) => Future.value(true)); - await onboardingService.authenticate(); - final atChops = onboardingService.atClient?.atChops; - expect(atChops, isNotNull); - expect(atChops?.atChopsKeys, isNotNull); - }); - test('A test to check whether at_chops keys are set correctly', () async { - final atSign = '@aliceđź› '; - AtOnboardingPreference onboardingPreference = AtOnboardingPreference() - ..atKeysFilePath = 'test/data/@aliceđź› .atKeys'; - AtOnboardingService onboardingService = - AtOnboardingServiceImpl(atSign, onboardingPreference); - onboardingService.atLookUp = mockAtLookup; - onboardingService.atClient = - await AtClientImpl.create(atSign, '.wavi', getAlicePreference()); - when(() => mockAtLookup.pkamAuthenticate()) - .thenAnswer((_) => Future.value(true)); - await onboardingService.authenticate(); - final atChops = onboardingService.atClient?.atChops; - expect(atChops, isNotNull); - expect(atChops!.atChopsKeys.atEncryptionKeyPair?.atPublicKey, isNotNull); - expect(atChops.atChopsKeys.atEncryptionKeyPair!.atPublicKey.publicKey, - at_demo.encryptionPublicKeyMap[atSign]); - expect(atChops.atChopsKeys.atEncryptionKeyPair?.atPrivateKey, isNotNull); - expect(atChops.atChopsKeys.atEncryptionKeyPair!.atPrivateKey.privateKey, - at_demo.encryptionPrivateKeyMap[atSign]); - expect(atChops.atChopsKeys.atPkamKeyPair?.atPublicKey, isNotNull); - expect(atChops.atChopsKeys.atPkamKeyPair!.atPublicKey.publicKey, - at_demo.pkamPublicKeyMap[atSign]); - expect(atChops.atChopsKeys.atPkamKeyPair?.atPrivateKey, isNotNull); - expect(atChops.atChopsKeys.atPkamKeyPair!.atPrivateKey.privateKey, - at_demo.pkamPrivateKeyMap[atSign]); + when(() => mockAtAuth.authenticate(any())).thenAnswer( + (_) => Future.value(AtAuthResponse(atSign)..isSuccessful = true)); + when(() => mockAtAuth.atChops) + .thenAnswer((_) => AtChopsImpl(AtChopsKeys())); + var authResult = await onboardingService.authenticate(); + expect(authResult, true); }); + //#TODO add more tests tearDown(() async => await tearDownFunc()); }); } @@ -71,8 +59,6 @@ Future tearDownFunc() async { } } -class MockAtLookupImpl extends Mock implements AtLookupImpl {} - AtClientPreference getAlicePreference() { var preference = AtClientPreference(); preference.hiveStoragePath = 'test/hive/client'; From cda9a8d231ae3bd83081dfe629d4ddaa2a28b8ff Mon Sep 17 00:00:00 2001 From: murali-shris Date: Thu, 12 Oct 2023 17:43:32 +0530 Subject: [PATCH 25/35] fix: dart analyzer issues --- .../example/apkam_authenticate.dart | 2 -- .../onboard/at_onboarding_service_impl.dart | 29 ++++++++----------- .../test/at_onboarding_cli_test.dart | 1 - .../pubspec.yaml | 14 ++++++++- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/packages/at_onboarding_cli/example/apkam_authenticate.dart b/packages/at_onboarding_cli/example/apkam_authenticate.dart index 445ea65e..2e2f1cf9 100644 --- a/packages/at_onboarding_cli/example/apkam_authenticate.dart +++ b/packages/at_onboarding_cli/example/apkam_authenticate.dart @@ -1,5 +1,3 @@ -import 'package:at_client/at_client.dart'; -import 'package:at_lookup/at_lookup.dart'; import 'package:at_onboarding_cli/at_onboarding_cli.dart'; import 'package:at_utils/at_logger.dart'; 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 63ae2044..ba458469 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 @@ -29,7 +29,6 @@ class AtOnboardingServiceImpl implements AtOnboardingService { bool _isAtsignOnboarded = false; AtSignLogger logger = AtSignLogger('OnboardingCli'); AtOnboardingPreference atOnboardingPreference; - AtClient? _atClient; AtLookUp? _atLookUp; final StreamController _pkamSuccessController = @@ -72,13 +71,14 @@ class AtOnboardingServiceImpl implements AtOnboardingService { _atLookUp?.enrollmentId = enrollmentId; _atLookUp?.signingAlgoType = atOnboardingPreference.signingAlgoType; _atLookUp?.hashingAlgoType = atOnboardingPreference.hashingAlgoType; - _atClient ??= atClientManager.atClient; + atClient ??= atClientManager.atClient; + _atLookUp!.atChops = atChops; } @override @Deprecated('Use getter') Future getAtClient() async { - return _atClient; + return atClient; } @override @@ -131,6 +131,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { await _generateAtKeysFile( atOnboardingResponse.enrollmentId, atOnboardingResponse.atAuthKeys!); } + _isAtsignOnboarded = atOnboardingResponse.isSuccessful; return _isAtsignOnboarded; } @@ -397,29 +398,29 @@ class AtOnboardingServiceImpl implements AtOnboardingService { AtAuthKeys atAuthKeys = _decryptAtKeysFile( (await _readAtKeysFile(atOnboardingPreference.atKeysFilePath))); //backup keys into local secondary - bool? response = await _atClient + bool? response = await atClient ?.getLocalSecondary() ?.putValue(AtConstants.atPkamPublicKey, atAuthKeys.apkamPublicKey!); logger.finer('PkamPublicKey persist to localSecondary: status $response'); // save pkam private key only when auth mode is keyFile. if auth mode is sim/any other secure element private key cannot be read and hence will not be part of keys file if (atOnboardingPreference.authMode == PkamAuthMode.keysFile) { - response = await _atClient + response = await atClient ?.getLocalSecondary() ?.putValue(AtConstants.atPkamPrivateKey, atAuthKeys.apkamPrivateKey!); logger .finer('PkamPrivateKey persist to localSecondary: status $response'); } - response = await _atClient?.getLocalSecondary()?.putValue( + response = await atClient?.getLocalSecondary()?.putValue( '${AtConstants.atEncryptionPublicKey}$_atSign', atAuthKeys.defaultEncryptionPublicKey!); logger.finer( 'EncryptionPublicKey persist to localSecondary: status $response'); - response = await _atClient?.getLocalSecondary()?.putValue( + response = await atClient?.getLocalSecondary()?.putValue( AtConstants.atEncryptionPrivateKey, atAuthKeys.defaultEncryptionPrivateKey!); logger.finer( 'EncryptionPrivateKey persist to localSecondary: status $response'); - response = await _atClient?.getLocalSecondary()?.putValue( + response = await atClient?.getLocalSecondary()?.putValue( AtConstants.atEncryptionSelfKey, atAuthKeys.defaultSelfEncryptionKey!); } @@ -433,8 +434,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { ..authMode = atOnboardingPreference.authMode; var atAuthResponse = await atAuth!.authenticate(atAuthRequest); logger.finer('Auth response: $atAuthResponse'); - if (!_isAtsignOnboarded && - atAuthResponse.isSuccessful && + if (atAuthResponse.isSuccessful && atOnboardingPreference.atKeysFilePath != null) { logger.finer('Calling persist keys to local secondary'); await _initAtClient(atAuth!.atChops!, @@ -617,7 +617,7 @@ class AtOnboardingServiceImpl implements AtOnboardingService { await (_atLookUp as AtLookupImpl).close(); } _atLookUp = null; - _atClient = null; + atClient = null; logger.info( 'Closing current instance of at_onboarding_cli (exit code: $exitCode)'); } @@ -629,18 +629,13 @@ class AtOnboardingServiceImpl implements AtOnboardingService { } @override - set atClient(AtClient? atClient) { - _atClient = atClient; - } + AtClient? atClient; @override set atLookUp(AtLookUp? atLookUp) { _atLookUp = atLookUp; } - @override - AtClient? get atClient => _atClient; - @override AtLookUp? get atLookUp => _atLookUp; diff --git a/packages/at_onboarding_cli/test/at_onboarding_cli_test.dart b/packages/at_onboarding_cli/test/at_onboarding_cli_test.dart index b06c7b00..40b4f6e5 100644 --- a/packages/at_onboarding_cli/test/at_onboarding_cli_test.dart +++ b/packages/at_onboarding_cli/test/at_onboarding_cli_test.dart @@ -9,7 +9,6 @@ import 'package:at_client/at_client.dart'; import 'package:at_auth/at_auth.dart'; import 'package:test/expect.dart'; import 'package:test/scaffolding.dart'; -import 'package:at_demo_data/at_demo_data.dart' as at_demo; class MockAtLookupImpl extends Mock implements AtLookupImpl {} diff --git a/tests/at_onboarding_cli_functional_tests/pubspec.yaml b/tests/at_onboarding_cli_functional_tests/pubspec.yaml index e7f0d13a..6db2eb4f 100644 --- a/tests/at_onboarding_cli_functional_tests/pubspec.yaml +++ b/tests/at_onboarding_cli_functional_tests/pubspec.yaml @@ -9,11 +9,23 @@ environment: dependencies: at_onboarding_cli: path: ../../packages/at_onboarding_cli - at_commons: ^3.0.55 + at_commons: ^3.0.56 at_utils: ^3.0.15 at_lookup: ^3.0.40 at_client: ^3.0.65 +dependency_overrides: + at_auth: + git: + url: https://github.com/atsign-foundation/at_libraries + path: packages/at_auth + ref: at_auth_new_package + at_chops: + git: + url: https://github.com/atsign-foundation/at_libraries + path: packages/at_chops + ref: at_auth_new_package + dev_dependencies: lints: ^1.0.0 test: ^1.17.2 From 1ae6f257c2d7e390bf51d5e3a365fca635ad7555 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Thu, 12 Oct 2023 18:44:13 +0530 Subject: [PATCH 26/35] fix: set useAtChops=true in functional test --- .../test/enrollment_test.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart index 496ba7c5..27051e6c 100644 --- a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart +++ b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart @@ -9,7 +9,6 @@ import 'package:at_utils/at_utils.dart'; import 'package:test/test.dart'; import 'package:at_onboarding_cli/src/util/at_onboarding_exceptions.dart'; - var pkamPublicKey; var pkamPrivateKey; var encryptionPublicKey; @@ -263,7 +262,8 @@ AtOnboardingPreference getPreferenceForAuth(String atSign) { '${Platform.environment['HOME']}/.atsign/keys/${atSign}_key.atKeys' ..appName = 'wavi' ..deviceName = 'pixel' - ..rootDomain = 'vip.ve.atsign.zone'; + ..rootDomain = 'vip.ve.atsign.zone' + ..useAtChops = true; return atOnboardingPreference; } @@ -278,7 +278,8 @@ AtOnboardingPreference getPreferenceForEnroll(String atSign) { ..appName = 'buzz' ..deviceName = 'iphone' ..rootDomain = 'vip.ve.atsign.zone' - ..apkamAuthRetryDurationMins = 1; + ..apkamAuthRetryDurationMins = 1 + ..useAtChops = true; return atOnboardingPreference; } From c85c0bd3fc49ecbd0a2b4255da6dd1be1437566d Mon Sep 17 00:00:00 2001 From: murali-shris Date: Fri, 13 Oct 2023 13:36:48 +0530 Subject: [PATCH 27/35] fix: replace pubspec with new branches for at_auth and at_chops --- .../lib/src/onboard/at_onboarding_service_impl.dart | 7 ++++--- packages/at_onboarding_cli/pubspec.yaml | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) 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 ba458469..359e8491 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 @@ -427,11 +427,12 @@ class AtOnboardingServiceImpl implements AtOnboardingService { @override Future authenticate({String? enrollmentId}) async { atAuth ??= AtAuthImpl(); - var atAuthRequest = AtAuthRequest(_atSign, - atOnboardingPreference.rootDomain, atOnboardingPreference.rootPort) + var atAuthRequest = AtAuthRequest(_atSign) ..enrollmentId = enrollmentId ..atKeysFilePath = atOnboardingPreference.atKeysFilePath - ..authMode = atOnboardingPreference.authMode; + ..authMode = atOnboardingPreference.authMode + ..rootDomain= atOnboardingPreference.rootDomain + ..rootPort=atOnboardingPreference.rootPort; var atAuthResponse = await atAuth!.authenticate(atAuthRequest); logger.finer('Auth response: $atAuthResponse'); if (atAuthResponse.isSuccessful && diff --git a/packages/at_onboarding_cli/pubspec.yaml b/packages/at_onboarding_cli/pubspec.yaml index 4041c51a..a900486b 100644 --- a/packages/at_onboarding_cli/pubspec.yaml +++ b/packages/at_onboarding_cli/pubspec.yaml @@ -27,17 +27,18 @@ dependencies: http: ^0.13.6 at_chops: ^1.0.4 +#TODO replace with published version dependency_overrides: at_auth: git: url: https://github.com/atsign-foundation/at_libraries path: packages/at_auth - ref: at_auth_new_package + ref: at_auth_new at_chops: git: url: https://github.com/atsign-foundation/at_libraries path: packages/at_chops - ref: at_auth_new_package + ref: at_chops_changes_at_auth dev_dependencies: lints: ^2.1.0 From 9fc1e29ee8f0624c6279061f4ec1805a412174c5 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Fri, 13 Oct 2023 13:42:12 +0530 Subject: [PATCH 28/35] fix: update functional tests pubspec with latest branch --- tests/at_onboarding_cli_functional_tests/pubspec.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/at_onboarding_cli_functional_tests/pubspec.yaml b/tests/at_onboarding_cli_functional_tests/pubspec.yaml index 6db2eb4f..59af0b7d 100644 --- a/tests/at_onboarding_cli_functional_tests/pubspec.yaml +++ b/tests/at_onboarding_cli_functional_tests/pubspec.yaml @@ -14,17 +14,18 @@ dependencies: at_lookup: ^3.0.40 at_client: ^3.0.65 +#TODO replace with published version dependency_overrides: at_auth: git: url: https://github.com/atsign-foundation/at_libraries path: packages/at_auth - ref: at_auth_new_package + ref: at_auth_new at_chops: git: url: https://github.com/atsign-foundation/at_libraries path: packages/at_chops - ref: at_auth_new_package + ref: at_chops_changes_at_auth dev_dependencies: lints: ^1.0.0 From 515a20bbe24cea3e503d706f3de93907e1517e67 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Fri, 13 Oct 2023 17:03:31 +0530 Subject: [PATCH 29/35] fix: replace pubspec with published version of at_chops and at_auth --- .../onboard/at_onboarding_service_impl.dart | 4 +-- packages/at_onboarding_cli/pubspec.yaml | 34 ++++++------------- 2 files changed, 13 insertions(+), 25 deletions(-) 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 359e8491..0e1065be 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 @@ -431,8 +431,8 @@ class AtOnboardingServiceImpl implements AtOnboardingService { ..enrollmentId = enrollmentId ..atKeysFilePath = atOnboardingPreference.atKeysFilePath ..authMode = atOnboardingPreference.authMode - ..rootDomain= atOnboardingPreference.rootDomain - ..rootPort=atOnboardingPreference.rootPort; + ..rootDomain = atOnboardingPreference.rootDomain + ..rootPort = atOnboardingPreference.rootPort; var atAuthResponse = await atAuth!.authenticate(atAuthRequest); logger.finer('Auth response: $atAuthResponse'); if (atAuthResponse.isSuccessful && diff --git a/packages/at_onboarding_cli/pubspec.yaml b/packages/at_onboarding_cli/pubspec.yaml index a900486b..47e02116 100644 --- a/packages/at_onboarding_cli/pubspec.yaml +++ b/packages/at_onboarding_cli/pubspec.yaml @@ -13,32 +13,20 @@ executables: at_activate: activate_cli dependencies: - at_utils: ^3.0.15 - at_client: ^3.0.65 - at_lookup: ^3.0.40 - zxing2: ^0.2.0 - image: ^4.0.17 + args: ^2.4.1 crypton: ^2.0.3 - at_commons: ^3.0.55 encrypt: ^5.0.1 - at_server_status: ^1.0.3 - path: ^1.8.1 - args: ^2.4.1 http: ^0.13.6 - at_chops: ^1.0.4 - -#TODO replace with published version -dependency_overrides: - at_auth: - git: - url: https://github.com/atsign-foundation/at_libraries - path: packages/at_auth - ref: at_auth_new - at_chops: - git: - url: https://github.com/atsign-foundation/at_libraries - path: packages/at_chops - ref: at_chops_changes_at_auth + image: ^4.0.17 + path: ^1.8.1 + zxing2: ^0.2.0 + at_auth: ^1.0.0 + at_chops: ^1.0.5 + at_client: ^3.0.65 + at_commons: ^3.0.56 + at_lookup: ^3.0.40 + at_server_status: ^1.0.3 + at_utils: ^3.0.15 dev_dependencies: lints: ^2.1.0 From 87a0efb3d25a5c6a390fa204d3e6df6d38b34d8c Mon Sep 17 00:00:00 2001 From: murali-shris Date: Fri, 13 Oct 2023 17:10:26 +0530 Subject: [PATCH 30/35] fix: replace at_chops and at_auth with published versions in pubspec --- .../pubspec.yaml | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/tests/at_onboarding_cli_functional_tests/pubspec.yaml b/tests/at_onboarding_cli_functional_tests/pubspec.yaml index 59af0b7d..d96f4aa9 100644 --- a/tests/at_onboarding_cli_functional_tests/pubspec.yaml +++ b/tests/at_onboarding_cli_functional_tests/pubspec.yaml @@ -9,23 +9,15 @@ environment: dependencies: at_onboarding_cli: path: ../../packages/at_onboarding_cli + at_auth: ^1.0.0 + at_chops: ^1.0.5 + at_client: ^3.0.65 at_commons: ^3.0.56 at_utils: ^3.0.15 at_lookup: ^3.0.40 - at_client: ^3.0.65 -#TODO replace with published version -dependency_overrides: - at_auth: - git: - url: https://github.com/atsign-foundation/at_libraries - path: packages/at_auth - ref: at_auth_new - at_chops: - git: - url: https://github.com/atsign-foundation/at_libraries - path: packages/at_chops - ref: at_chops_changes_at_auth + + dev_dependencies: lints: ^1.0.0 From d6e7f8b21b27653f903d10448c76844369a8b8ef Mon Sep 17 00:00:00 2001 From: murali-shris Date: Tue, 24 Oct 2023 15:20:46 +0530 Subject: [PATCH 31/35] build: at_client latest trunk changes --- packages/at_onboarding_cli/pubspec.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/at_onboarding_cli/pubspec.yaml b/packages/at_onboarding_cli/pubspec.yaml index 47e02116..d2fcb408 100644 --- a/packages/at_onboarding_cli/pubspec.yaml +++ b/packages/at_onboarding_cli/pubspec.yaml @@ -23,11 +23,18 @@ dependencies: at_auth: ^1.0.0 at_chops: ^1.0.5 at_client: ^3.0.65 - at_commons: ^3.0.56 + at_commons: ^3.0.57 at_lookup: ^3.0.40 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: trunk + dev_dependencies: lints: ^2.1.0 test: ^1.24.2 From 5b077339474ebe9e4e61a2d554c583acb9101c88 Mon Sep 17 00:00:00 2001 From: murali-shris Date: Tue, 24 Oct 2023 15:32:27 +0530 Subject: [PATCH 32/35] build: replace at_client trunk with published version --- packages/at_onboarding_cli/pubspec.yaml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/at_onboarding_cli/pubspec.yaml b/packages/at_onboarding_cli/pubspec.yaml index d2fcb408..d1a22a41 100644 --- a/packages/at_onboarding_cli/pubspec.yaml +++ b/packages/at_onboarding_cli/pubspec.yaml @@ -22,19 +22,12 @@ dependencies: zxing2: ^0.2.0 at_auth: ^1.0.0 at_chops: ^1.0.5 - at_client: ^3.0.65 + at_client: ^3.0.66 at_commons: ^3.0.57 at_lookup: ^3.0.40 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: trunk - dev_dependencies: lints: ^2.1.0 test: ^1.24.2 From 1fcd19dc6ae43ab07d2d4bb1b7ae10de4d66f8ad Mon Sep 17 00:00:00 2001 From: murali-shris Date: Thu, 26 Oct 2023 13:27:11 +0530 Subject: [PATCH 33/35] 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 From dd9d1b76462d249bf66242b8c47c736f6bcfd89a Mon Sep 17 00:00:00 2001 From: murali-shris Date: Thu, 26 Oct 2023 14:02:13 +0530 Subject: [PATCH 34/35] fix: checkin missed file --- .../example/atsign_preference.dart | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 packages/at_onboarding_cli/example/atsign_preference.dart diff --git a/packages/at_onboarding_cli/example/atsign_preference.dart b/packages/at_onboarding_cli/example/atsign_preference.dart new file mode 100644 index 00000000..c37f2ee2 --- /dev/null +++ b/packages/at_onboarding_cli/example/atsign_preference.dart @@ -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 _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(); + } +} From f00c6503e6c30508bdc4df4f19fbc5e53b39f82f Mon Sep 17 00:00:00 2001 From: murali-shris Date: Thu, 26 Oct 2023 15:34:33 +0530 Subject: [PATCH 35/35] fix: functional test issue --- tests/at_onboarding_cli_functional_tests/pubspec.yaml | 11 +++++++++-- .../test/enrollment_test.dart | 11 +++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/tests/at_onboarding_cli_functional_tests/pubspec.yaml b/tests/at_onboarding_cli_functional_tests/pubspec.yaml index d96f4aa9..27776540 100644 --- a/tests/at_onboarding_cli_functional_tests/pubspec.yaml +++ b/tests/at_onboarding_cli_functional_tests/pubspec.yaml @@ -11,11 +11,18 @@ dependencies: path: ../../packages/at_onboarding_cli at_auth: ^1.0.0 at_chops: ^1.0.5 - at_client: ^3.0.65 - at_commons: ^3.0.56 + at_client: ^3.0.66 + at_commons: ^3.0.57 at_utils: ^3.0.15 at_lookup: ^3.0.40 +dependency_overrides: + at_client: + git: + url: https://github.com/atsign-foundation/at_client_sdk/ + path: packages/at_client + ref: enrollmentid_bug + diff --git a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart index 27051e6c..80acb827 100644 --- a/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart +++ b/tests/at_onboarding_cli_functional_tests/test/enrollment_test.dart @@ -202,11 +202,11 @@ Future _notificationCallback( final encryptedApkamSymmetricKey = jsonDecode(notification.value!)['encryptedApkamSymmetricKey']; var encryptionPrivateKey = - await atClient.getLocalSecondary()!.getEncryptionPrivateKey()!; + await atClient.getLocalSecondary()!.getEncryptionPrivateKey(); var selfEncryptionKey = await atClient.getLocalSecondary()!.getEncryptionSelfKey(); final apkamSymmetricKey = EncryptionUtil.decryptKey( - encryptedApkamSymmetricKey, encryptionPrivateKey); + encryptedApkamSymmetricKey, encryptionPrivateKey!); var encryptedDefaultPrivateEncKey = EncryptionUtil.encryptValue(encryptionPrivateKey, apkamSymmetricKey); var encryptedDefaultSelfEncKey = @@ -262,8 +262,7 @@ AtOnboardingPreference getPreferenceForAuth(String atSign) { '${Platform.environment['HOME']}/.atsign/keys/${atSign}_key.atKeys' ..appName = 'wavi' ..deviceName = 'pixel' - ..rootDomain = 'vip.ve.atsign.zone' - ..useAtChops = true; + ..rootDomain = 'vip.ve.atsign.zone'; return atOnboardingPreference; } @@ -278,8 +277,8 @@ AtOnboardingPreference getPreferenceForEnroll(String atSign) { ..appName = 'buzz' ..deviceName = 'iphone' ..rootDomain = 'vip.ve.atsign.zone' - ..apkamAuthRetryDurationMins = 1 - ..useAtChops = true; + ..apkamAuthRetryDurationMins = 1; + return atOnboardingPreference; }