OTAuthKitAdapter.m   [plain text]

#if OCTAGON

#import "OTAuthKitAdapter.h"
#import "OTConstants.h"

#import "utilities/SecCFError.h"
#import "keychain/categories/NSError+UsefulConstructors.h"

#import <AppleAccount/AppleAccount.h>
#import <AppleAccount/AppleAccount_Private.h>
#import <AuthKit/AuthKit.h>
#import <AuthKit/AuthKit_Private.h>
#import <AuthKit/AKDeviceListDeltaMessagePayload.h>
#import <Foundation/NSDistributedNotificationCenter.h>
#import "keychain/ckks/CKKSListenerCollection.h"
#import "keychain/ckks/CKKSAnalytics.h"

#include "utilities/SecABC.h"

#import <AppleAccount/ACAccount+AppleAccount.h>

@interface OTAuthKitActualAdapter ()
@property CKKSListenerCollection<OTAuthKitAdapterNotifier>* notifiers;
@end

@implementation OTAuthKitActualAdapter

- (NSString* _Nullable)primaryiCloudAccountAltDSID:(NSError **)error
{
ACAccountStore *store = [[ACAccountStore alloc] init];
ACAccount* primaryAccount = [store aa_primaryAppleAccount];
if(!primaryAccount) {
secnotice("authkit", "No primary account");
if (error) {
*error = [NSError errorWithDomain:OctagonErrorDomain
code:OTAuthKitNoPrimaryAccount
description:@"No primary account"];
}
return nil;
}

NSString *altDSID = [primaryAccount aa_altDSID];
if (altDSID == NULL) {
secnotice("authkit", "No altDSID on primary account");
if (error) {
*error = [NSError errorWithDomain:OctagonErrorDomain
code:OTAuthKitPrimaryAccountHaveNoDSID
description:@"No altdsid on primary account"];
}
}
return altDSID;
}

- (BOOL)accountIsHSA2ByAltDSID:(NSString*)altDSID
{
BOOL hsa2 = NO;

AKAccountManager *manager = [AKAccountManager sharedInstance];
ACAccount *authKitAccount = [manager authKitAccountWithAltDSID:altDSID];
AKAppleIDSecurityLevel securityLevel = [manager securityLevelForAccount:authKitAccount];
if(securityLevel == AKAppleIDSecurityLevelHSA2) {
hsa2 = YES;
}
secnotice("security-authkit", "Security level for altDSID %@ is %lu", altDSID, (unsigned long)securityLevel);
return hsa2;
}

- (BOOL)accountIsDemoAccount:(NSError**)error
{
NSError* localError = nil;
NSString* altDSID = [self primaryiCloudAccountAltDSID:&localError];

if(altDSID == nil) {
secerror("octagon-authkit:could not retrieve altDSID");
}
if (localError) {
secerror("octagon-authkit: hit an error retrieving altDSID: %@", localError);
if(error){
*error = localError;
}
return NO;
}

AKAccountManager *manager = [AKAccountManager sharedInstance];
ACAccount *authKitAccount = [manager authKitAccountWithAltDSID:altDSID];
BOOL isDemo = [manager demoAccountForAccount:authKitAccount];

secnotice("security-authkit", "Account with altDSID %@ is a demo account: %@", altDSID, isDemo ? @"true" : @"false");

return isDemo;
}

- (NSString* _Nullable)machineID:(NSError**)error
{
AKAnisetteProvisioningController* anisetteController = [[AKAnisetteProvisioningController alloc] init];
NSError* localError = nil;
AKAnisetteData* anisetteData = [anisetteController anisetteDataWithError:&localError];
if(!anisetteData) {
secnotice("authkit", "Unable to fetch data: %@", localError);
if(error) {
*error = localError;
}
return nil;
}

NSString* machineID = anisetteData.machineID;
if(!machineID) {
secnotice("authkit", "Anisette data does not have machineID");
if(error) {
[SecABC triggerAutoBugCaptureWithType:@"AuthKit" subType:@"missingMID"];
*error = [NSError errorWithDomain:OctagonErrorDomain
code:OTAuthKitMachineIDMissing
description:@"Anisette data does not have machineID"];
}
return nil;
}

secnotice("authkit", "fetched current machine ID as: %@", machineID);

return machineID;
}

- (void)fetchCurrentDeviceList:(void (^)(NSSet<NSString*>* _Nullable machineIDs, NSError* _Nullable error))complete
{
AKDeviceListRequestContext* context = [[AKDeviceListRequestContext alloc] init];
if (context == nil) {
NSError *error = [NSError errorWithDomain:OctagonErrorDomain
code:OTAuthKitAKDeviceListRequestContextClass
description:@"can't get AKDeviceListRequestContextClass"];
[[CKKSAnalytics logger] logUnrecoverableError:error forEvent:OctagonEventAuthKitDeviceList withAttributes:nil];
complete(nil, error);
return;
}
NSError *authKitError = nil;
context.altDSID = [self primaryiCloudAccountAltDSID:&authKitError];
if (context.altDSID == NULL) {
secnotice("authkit", "Failed to get primary account AltDSID: %@", authKitError);
NSError *error = [NSError errorWithDomain:OctagonErrorDomain
code:OTAuthKitPrimaryAccountHaveNoDSID
description:@"Can't get primary AltDSID"
underlying:authKitError];
[[CKKSAnalytics logger] logUnrecoverableError:error forEvent:OctagonEventAuthKitDeviceList withAttributes:nil];
[SecABC triggerAutoBugCaptureWithType:@"AuthKit" subType:@"missingAltDSID"];
complete(nil, error);
return;
}

AKAppleIDAuthenticationController *authController = [[AKAppleIDAuthenticationController alloc] init];
if(authController == nil) {
NSError *error = [NSError errorWithDomain:OctagonErrorDomain
code:OTAuthKitNoAuthenticationController
description:@"can't get authController"];
[[CKKSAnalytics logger] logUnrecoverableError:error forEvent:OctagonEventAuthKitDeviceList withAttributes:nil];
complete(nil, error);
return;
}

[authController fetchDeviceListWithContext:context completion:^(NSArray<AKRemoteDevice *> *deviceList, NSError *error) {
if (deviceList) {
NSMutableSet *mids = [[NSMutableSet alloc] init];

for (AKRemoteDevice *device in deviceList) {
[mids addObject:device.machineId];
}

secnotice("authkit", "Current machine ID list: %@", mids);
complete(mids, error);
[[CKKSAnalytics logger] logSuccessForEventNamed:OctagonEventAuthKitDeviceList];

} else {
[[CKKSAnalytics logger] logUnrecoverableError:error forEvent:OctagonEventAuthKitDeviceList withAttributes:nil];
secnotice("authkit", "received no device list: %@", error);
complete(nil, error);
}
}];
}

- (void)registerNotification:(id<OTAuthKitAdapterNotifier>)newNotifier
{
if (self.notifiers == nil) {
self.notifiers = [[CKKSListenerCollection<OTAuthKitAdapterNotifier> alloc] initWithName:@"otauthkitadapter-notifiers"];
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(notifyAKDeviceList:) name:AKDeviceListChangedNotification object:nil];
}
[self.notifiers registerListener:newNotifier];
}

- (void)notifyAKDeviceList:(NSNotification* _Nullable)notification
{
AKDeviceListDeltaMessagePayload *payload = nil;
NSDictionary *userInfo = nil;
if (notification != nil) {
userInfo = [notification userInfo];
if (userInfo != nil) {
payload = [[AKDeviceListDeltaMessagePayload alloc] initWithResponseBody:userInfo];
}
}

secnotice("authkit", "received notifyAKDeviceList: %@, read payload: %@",
notification.userInfo,
// Logging the payload logs an address, so clean it up here.
payload ? @"YES" : @"NO");

[self.notifiers iterateListeners:^(id<OTAuthKitAdapterNotifier> listener) {
NSString* altDSID = payload.altDSID;
NSArray<NSString*>* machineIDs = payload.machineIDs;

if (altDSID == nil || machineIDs == nil || machineIDs.count == 0) {
secnotice("authkit", "partial push or no machine IDs in list; treating as incomplete");
[listener incompleteNotificationOfMachineIDListChange];
return;
}
switch (payload.operation) {
case AKDeviceListDeltaOperationAdd:
[listener machinesAdded:machineIDs altDSID:altDSID];
return;
break;
case AKDeviceListDeltaOperationRemove:
[listener machinesRemoved:machineIDs altDSID:altDSID];
return;
break;
case AKDeviceListDeltaOperationUnknown:
default:
break;
}
[listener incompleteNotificationOfMachineIDListChange];
}];
}

@end

#endif // OCTAGON