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: 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: 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: }
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: 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: 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