Skip to content

Commit

Permalink
Merge pull request #461 from atsign-foundation/apkam_atonboarding_lat…
Browse files Browse the repository at this point in the history
…est_atauth

feat: enrollment code changes for reusability between at_client_mobile and at_onboarding_cli
  • Loading branch information
murali-shris authored Dec 7, 2023
2 parents b571d24 + 3819c39 commit 5e6e9de
Show file tree
Hide file tree
Showing 14 changed files with 439 additions and 246 deletions.
2 changes: 2 additions & 0 deletions packages/at_auth/lib/at_auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ export 'src/enroll/at_enrollment_base.dart';
export 'src/enroll/at_enrollment_impl.dart';
export 'src/enroll/at_enrollment_response.dart';
export 'src/enroll/at_enrollment_request.dart';
export 'src/enroll/at_initial_enrollment_request.dart';
export 'src/enroll/at_new_enrollment_request.dart';
8 changes: 6 additions & 2 deletions packages/at_auth/lib/src/at_auth_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:at_auth/src/at_auth_base.dart';
import 'package:at_auth/src/auth/cram_authenticator.dart';
import 'package:at_auth/src/auth/pkam_authenticator.dart';
import 'package:at_auth/src/enroll/at_enrollment_base.dart';
import 'package:at_auth/src/enroll/at_initial_enrollment_request.dart';
import 'package:at_auth/src/keys/at_auth_keys.dart';
import 'package:at_auth/src/onboard/at_onboarding_request.dart';
import 'package:at_auth/src/onboard/at_onboarding_response.dart';
Expand Down Expand Up @@ -228,13 +229,16 @@ class AtAuthImpl implements AtAuth {
encryptionAlgorithm: symmetricEncryptionAlgo,
iv: AtChopsUtil.generateIVLegacy())
.result;
var enrollRequestBuilder = AtEnrollmentRequest.request()
_logger.finer('apkamPublicKey: ${atAuthKeys.apkamPublicKey}');
var enrollRequestBuilder = AtInitialEnrollmentRequestBuilder()
..setAppName(atOnboardingRequest.appName)
..setDeviceName(atOnboardingRequest.deviceName)
..setEncryptedDefaultEncryptionPrivateKey(
encryptedDefaultEncryptionPrivateKey)
..setEncryptedDefaultSelfEncryptionKey(encryptedDefaultSelfEncryptionKey)
..setApkamPublicKey(atAuthKeys.apkamPublicKey);
..setApkamPublicKey(atAuthKeys.apkamPublicKey)
..setAtAuthKeys(atAuthKeys)
..setEnrollOperationEnum(EnrollOperationEnum.request);
atEnrollmentBase ??= AtEnrollmentImpl(atOnboardingRequest.atSign);
AtEnrollmentResponse enrollmentResponse;
try {
Expand Down
156 changes: 93 additions & 63 deletions packages/at_auth/lib/src/enroll/at_enrollment_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';

import 'package:at_auth/at_auth.dart';
import 'package:at_auth/src/enroll/at_enrollment_notification_request.dart';
import 'package:at_chops/at_chops.dart';
import 'package:at_commons/at_builders.dart';
import 'package:at_commons/at_commons.dart';
Expand All @@ -22,38 +23,55 @@ class AtEnrollmentImpl implements AtEnrollmentBase {
@override
Future<AtEnrollmentResponse> submitEnrollment(
AtEnrollmentRequest atEnrollmentRequest, AtLookUp atLookUp) async {
switch (atEnrollmentRequest.runtimeType) {
case AtInitialEnrollmentRequest:
return await _initialClientEnrollment(
atEnrollmentRequest as AtInitialEnrollmentRequest, atLookUp);
case AtNewEnrollmentRequest:
return await _newClientEnrollment(
atEnrollmentRequest as AtNewEnrollmentRequest, atLookUp);
default:
throw AtEnrollmentException(
'Invalid AtEnrollmentRequest type: ${atEnrollmentRequest.runtimeType}');
}
}

Future<AtEnrollmentResponse> _initialClientEnrollment(
AtInitialEnrollmentRequest atInitialEnrollmentRequest,
AtLookUp atLookUp) async {
_logger.finer('inside initialClientEnrollment');
final atAuthKeys = atInitialEnrollmentRequest.atAuthKeys;
var enrollVerbBuilder = createEnrollVerbBuilder(atInitialEnrollmentRequest);
var enrollResult = await _executeEnrollCommand(enrollVerbBuilder, atLookUp);
_logger.finer('enrollResult: $enrollResult');
var enrollJson = jsonDecode(enrollResult);
var enrollmentIdFromServer = enrollJson[AtConstants.enrollmentId];
var enrollStatus = getEnrollStatusFromString(enrollJson['status']);
atAuthKeys!.enrollmentId = enrollmentIdFromServer;
return AtEnrollmentResponse(enrollmentIdFromServer, enrollStatus)
..atAuthKeys = atAuthKeys;
}

Future<AtEnrollmentResponse> _newClientEnrollment(
AtNewEnrollmentRequest atNewEnrollmentRequest, AtLookUp atLookUp) async {
_logger.info('Generating APKAM encryption keypair and APKAM symmetric key');
AtPkamKeyPair atPkamKeyPair = AtChopsUtil.generateAtPkamKeyPair();
SymmetricKey apkamSymmetricKey =
AtChopsUtil.generateSymmetricKey(EncryptionKeyType.aes256);

return await enrollInternal(
atEnrollmentRequest, atLookUp, atPkamKeyPair, apkamSymmetricKey);
}

@visibleForTesting
Future<AtEnrollmentResponse> enrollInternal(
AtEnrollmentRequest atEnrollmentRequest,
AtLookUp atLookUp,
AtPkamKeyPair atPkamKeyPair,
SymmetricKey apkamSymmetricKey) async {
// default flow
String defaultEncryptionPublicKey =
await _getDefaultEncryptionPublicKey(atLookUp);
// Encrypting the Encryption Public key with APKAM Symmetric key.
String encryptedApkamSymmetricKey =
RSAPublicKey.fromString(defaultEncryptionPublicKey)
.encrypt(apkamSymmetricKey.key);

EnrollResponse enrollmentResponse = await _sendEnrollmentRequest(
atLookUp,
atEnrollmentRequest.appName,
atEnrollmentRequest.deviceName,
atEnrollmentRequest.otp,
atEnrollmentRequest.namespaces,
atPkamKeyPair.atPublicKey.publicKey,
encryptedApkamSymmetricKey,
);

var enrollVerbBuilder = createEnrollVerbBuilder(atNewEnrollmentRequest,
atPkamKeyPair: atPkamKeyPair,
encryptedApkamSymmetricKey: encryptedApkamSymmetricKey);
var enrollResult = await _executeEnrollCommand(enrollVerbBuilder, atLookUp);
var enrollJson = jsonDecode(enrollResult);
var enrollmentIdFromServer = enrollJson[AtConstants.enrollmentId];
var enrollStatus = getEnrollStatusFromString(enrollJson['status']);
AtChopsKeys atChopsKeys = AtChopsKeys.create(
AtEncryptionKeyPair.create(defaultEncryptionPublicKey, ''),
AtPkamKeyPair.create(atPkamKeyPair.atPublicKey.publicKey,
Expand All @@ -66,13 +84,10 @@ class AtEnrollmentImpl implements AtEnrollmentBase {
..apkamPublicKey = atPkamKeyPair.atPublicKey.publicKey
..defaultEncryptionPublicKey = defaultEncryptionPublicKey
..apkamSymmetricKey = apkamSymmetricKey.key
..enrollmentId = enrollmentResponse.enrollmentId;
..enrollmentId = enrollmentIdFromServer;

// The EnrollmentSubmissionResponse has atChopsKeys which contains the APKAM
// keys and APKAM Symmetric key which will be persisted by the client requesting the
// enrollment.
AtEnrollmentResponse atEnrollmentResponse = AtEnrollmentResponse(
enrollmentResponse.enrollmentId, enrollmentResponse.enrollStatus);
AtEnrollmentResponse atEnrollmentResponse =
AtEnrollmentResponse(enrollmentIdFromServer, enrollStatus);
atEnrollmentResponse.atAuthKeys = atAuthKeys;
return atEnrollmentResponse;
}
Expand All @@ -90,43 +105,15 @@ class AtEnrollmentImpl implements AtEnrollmentBase {
return defaultEncryptionPublicKey;
}

Future<EnrollResponse> _sendEnrollmentRequest(
AtLookUp atLookUp,
String? appName,
String? deviceName,
String? otp,
Map<String, String>? namespaces,
String apkamPublicKey,
String encryptedApkamSymmetricKey,
) async {
var enrollVerbBuilder = EnrollVerbBuilder()
..appName = appName
..deviceName = deviceName
..namespaces = namespaces
..otp = otp
..apkamPublicKey = apkamPublicKey
..encryptedAPKAMSymmetricKey = encryptedApkamSymmetricKey;
var enrollResult =
await atLookUp.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[AtConstants.enrollmentId];

return EnrollResponse(enrollmentIdFromServer,
getEnrollStatusFromString(enrollJson['status']));
}

@override
Future<AtEnrollmentResponse> manageEnrollmentApproval(
AtEnrollmentRequest atEnrollmentRequest, AtLookUp atLookUp) {
switch (atEnrollmentRequest.enrollOperationEnum) {
case EnrollOperationEnum.approve:
if (atEnrollmentRequest is! AtEnrollmentNotificationRequest) {
throw AtEnrollmentException(
'Invalid atEnrollmentRequest type: $atEnrollmentRequest. Please pass AtEnrollmentNotificationRequest');
}
return _handleApproveOperation(atEnrollmentRequest, atLookUp);
case EnrollOperationEnum.deny:
return _handleDenyOperation(atEnrollmentRequest, atLookUp);
Expand All @@ -136,16 +123,17 @@ class AtEnrollmentImpl implements AtEnrollmentBase {
}

Future<AtEnrollmentResponse> _handleApproveOperation(
AtEnrollmentRequest atEnrollmentRequest, AtLookUp atLookUp) async {
AtEnrollmentNotificationRequest atEnrollmentNotificationRequest,
AtLookUp atLookUp) async {
// Decrypt the encrypted APKAM Symmetric key
var defaultEncryptionPrivateKey = RSAPrivateKey.fromString(atLookUp
.atChops!.atChopsKeys.atEncryptionKeyPair!.atPrivateKey.privateKey);
var apkamSymmetricKey = defaultEncryptionPrivateKey
.decrypt(atEnrollmentRequest.encryptedAPKAMSymmetricKey!);
.decrypt(atEnrollmentNotificationRequest.encryptedApkamSymmetricKey);
atLookUp.atChops?.atChopsKeys.apkamSymmetricKey = AESKey(apkamSymmetricKey);

String command = 'enroll:approve:${jsonEncode({
'enrollmentId': atEnrollmentRequest.enrollmentId,
'enrollmentId': atEnrollmentNotificationRequest.enrollmentId,
'encryptedDefaultEncryptedPrivateKey': atLookUp.atChops
?.encryptString(
atLookUp.atChops!.atChopsKeys.atEncryptionKeyPair!
Expand Down Expand Up @@ -203,4 +191,46 @@ class AtEnrollmentImpl implements AtEnrollmentBase {
enrollmentJsonMap['enrollmentId'], enrollmentJsonMap['status']);
return enrollmentResponse;
}

@visibleForTesting
/// Creates a verb builder instance based on the [request] type
EnrollVerbBuilder createEnrollVerbBuilder(
AtEnrollmentRequest request, {
AtPkamKeyPair? atPkamKeyPair,
String? encryptedApkamSymmetricKey,
}) {
var enrollVerbBuilder = EnrollVerbBuilder()
..appName = request.appName
..deviceName = request.deviceName
..namespaces = request.namespaces;

if (request is AtInitialEnrollmentRequest) {
enrollVerbBuilder
..encryptedDefaultEncryptionPrivateKey =
request.encryptedDefaultEncryptionPrivateKey
..encryptedDefaultSelfEncryptionKey =
request.encryptedDefaultSelfEncryptionKey
..apkamPublicKey = request.apkamPublicKey;
} else if (request is AtNewEnrollmentRequest) {
enrollVerbBuilder
..otp = request.otp
..apkamPublicKey = atPkamKeyPair!.atPublicKey.publicKey
..encryptedAPKAMSymmetricKey = encryptedApkamSymmetricKey;
}

return enrollVerbBuilder;
}

Future<String> _executeEnrollCommand(
EnrollVerbBuilder enrollVerbBuilder, AtLookUp atLookUp) async {
var enrollResult =
await atLookUp.executeCommand(enrollVerbBuilder.buildCommand());
if (enrollResult == null ||
enrollResult.isEmpty ||
enrollResult.startsWith('error:')) {
throw AtEnrollmentException(
'Enrollment response from server: $enrollResult');
}
return enrollResult.replaceFirst('data:', '');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'package:at_auth/src/enroll/at_enrollment_request.dart';

/// In APKAM approval flow, use this class from a privileged client to set attributes required for enrollment approval.
/// Once a notification is received on the privileged client which can approve enrollment notifications from new devices,
/// use [AtEnrollmentNotificationRequestBuilder] to create [AtEnrollmentNotificationRequest]
class AtEnrollmentNotificationRequest extends AtEnrollmentRequest {
String _encryptedApkamSymmetricKey;

String get encryptedApkamSymmetricKey => _encryptedApkamSymmetricKey;

AtEnrollmentNotificationRequest.builder(
AtEnrollmentNotificationRequestBuilder
atEnrollmentNotificationRequestBuilder)
: _encryptedApkamSymmetricKey =
atEnrollmentNotificationRequestBuilder._encryptedApkamSymmetricKey,
super.builder(atEnrollmentNotificationRequestBuilder);
}

class AtEnrollmentNotificationRequestBuilder
extends AtEnrollmentRequestBuilder {
late String _encryptedApkamSymmetricKey;

AtEnrollmentNotificationRequestBuilder setEncryptedApkamSymmetricKey(
String encryptedApkamSymmetricKey) {
_encryptedApkamSymmetricKey = encryptedApkamSymmetricKey;
return this;
}

/// Builds and returns an instance of [AtEnrollmentNotificationRequest].
AtEnrollmentNotificationRequest build() {
return AtEnrollmentNotificationRequest.builder(this);
}
}
Loading

0 comments on commit 5e6e9de

Please sign in to comment.