EscrowRequestInformCloudServicesOperation.m   [plain text]



#import <CloudServices/SecureBackup.h>

#import "utilities/debugging.h"
#import "keychain/ckks/CKKSLockStateTracker.h"

#import "keychain/escrowrequest/EscrowRequestController.h"
#import "keychain/escrowrequest/operations/EscrowRequestInformCloudServicesOperation.h"
#import "keychain/escrowrequest/generated_source/SecEscrowPendingRecord.h"
#import "keychain/escrowrequest/SecEscrowPendingRecord+KeychainSupport.h"

@interface EscrowRequestInformCloudServicesOperation()
@property CKKSLockStateTracker* lockStateTracker;
@end

@implementation EscrowRequestInformCloudServicesOperation
@synthesize nextState = _nextState;
@synthesize intendedState = _intendedState;

- (instancetype)initWithIntendedState:(OctagonState*)intendedState
                           errorState:(OctagonState*)errorState
                     lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
{
    if((self = [super init])) {
        _intendedState = intendedState;
        _nextState = errorState;
        _lockStateTracker = lockStateTracker;
    }
    return self;
}

- (void)main
{
    secnotice("escrowrequest", "Telling CloudServices about any pending requests");

    NSError* error = nil;
    NSArray<SecEscrowPendingRecord*>* records = [SecEscrowPendingRecord loadAllFromKeychain:&error];
    if(error && !([error.domain isEqualToString:NSOSStatusErrorDomain] && error.code == errSecItemNotFound)) {
        secnotice("escrowrequest", "failed to fetch records from keychain: %@", error);
        if([self.lockStateTracker isLockedError:error]) {
            secnotice("escrowrequest", "Trying again after unlock");
            self.nextState = EscrowRequestStateWaitForUnlock;
        } else {
            self.nextState = EscrowRequestStateNothingToDo;
        }
        self.error = error;
        return;
    }
    error = nil;

    SecEscrowPendingRecord* record = nil;

    for(SecEscrowPendingRecord* existingRecord in records) {
        if(!existingRecord.hasCertCached) {
            record = existingRecord;
            break;
        }
    }

    if(!record) {
        secnotice("escrowrequest", "No pending escrow request needs a certificate");
        self.nextState = EscrowRequestStateNothingToDo;
        return;
    }

    // Next, see if CloudServices can cache a certificate
    NSData* cachedCert = [EscrowRequestInformCloudServicesOperation triggerCloudServicesPasscodeRequest:record.uuid error:&error];
    record.lastCloudServicesTriggerTime = (uint64_t) ([[NSDate date] timeIntervalSince1970] * 1000);

    if(!cachedCert || error) {
        secerror("escrowrequest: cloudservices reports an issue caching the certificate, so we'll have to try again later: %@", error);
        self.error = error;
        // TODO: wait for network?
        self.nextState = EscrowRequestStateNothingToDo;

        NSError* saveCacheTimeError = nil;
        [record saveToKeychain:&saveCacheTimeError];
        if(saveCacheTimeError) {
            secerror("escrowrequest: unable to save the last attempt time: %@", saveCacheTimeError);
        }

        return;
    }

    record.certCached = true;
    [record saveToKeychain:&error];

    if(error) {
        // Ignore this error, since we've successfully triggered the update. We'll probably re-cache the certificate later, but that's okay.
        secerror("escrowrequest: unable to save escrow update request certificate status, so we'll have to try again later: %@", error);
        self.error = error;

        if([self.lockStateTracker isLockedError:error]) {
            secnotice("escrowrequest", "Trying again after unlock");
            self.nextState = EscrowRequestStateWaitForUnlock;
        } else {
            self.nextState = EscrowRequestStateNothingToDo;
        }

        return;
    }

    secnotice("escrowrequest", "CloudService successfully cached a certificate; request is ready for passcode");
    self.nextState = EscrowRequestStateNothingToDo;
}

// Separated into a class method for mocking
// Returns any cert that CS has cached
+ (NSData* _Nullable)triggerCloudServicesPasscodeRequest:(NSString*)uuid error:(NSError**)error
{
    SecureBackup* sb = [[SecureBackup alloc] init];

    NSError* localError = nil;
    SecureBackupBeginPasscodeRequestResults* results = [sb beginHSA2PasscodeRequest:true
                                                                               uuid:uuid
                                                                              error:error];

    if(!results || localError) {
        secerror("escrowrequest: unable to begin passcode request: %@", localError);
        if(error) {
            *error = localError;
        }
        return nil;
    }

    if(!results.cert) {
        secerror("escrowrequest: sbd failed to cache a certificate");
        // TODO fill in error
        return nil;
    }

    return results.cert;
}

@end