EscrowRequestPerformEscrowEnrollOperation.m [plain text]
#import <CoreCDP/CDPError.h>
#import <CoreCDP/CDPStateController.h>
#import <CloudServices/CloudServices.h>
#import "utilities/debugging.h"
#import "keychain/ot/ObjCImprovements.h"
#import "keychain/escrowrequest/EscrowRequestController.h"
#import "keychain/escrowrequest/operations/EscrowRequestPerformEscrowEnrollOperation.h"
#import "keychain/escrowrequest/generated_source/SecEscrowPendingRecord.h"
#import "keychain/escrowrequest/SecEscrowPendingRecord+KeychainSupport.h"
#import "keychain/ckks/CKKSLockStateTracker.h"
@interface EscrowRequestPerformEscrowEnrollOperation ()
@property bool enforceRateLimiting;
@property CKKSLockStateTracker* lockStateTracker;
@end
@implementation EscrowRequestPerformEscrowEnrollOperation
@synthesize nextState = _nextState;
@synthesize intendedState = _intendedState;
- (instancetype)initWithIntendedState:(OctagonState*)intendedState
errorState:(OctagonState*)errorState
enforceRateLimiting:(bool)enforceRateLimiting
lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
{
if((self = [super init])) {
_intendedState = intendedState;
_nextState = errorState;
_enforceRateLimiting = enforceRateLimiting;
_lockStateTracker = lockStateTracker;
}
return self;
}
- (BOOL)checkFatalError:(NSError *)error
{
if (error == nil) {
return NO;
}
if (error.code == kSecureBackupInternalError && [error.domain isEqualToString:kSecureBackupErrorDomain]) { // SOS peer ID mismatch!!!, the error code is wrong though
return YES;
}
if ([error.domain isEqualToString:kSecureBackupErrorDomain] && error.code == kSecureBackupNotInSyncCircleError) {
// One or more peers is missing (likely the SOS peer)
return YES;
}
if( [error.domain isEqualToString:CDPStateErrorDomain] && error.code == CDPStateErrorNoPeerIdFound) {
// CDP is unhappy about the self peer. I don't understand why we get both this and kSecureBackupNotInSyncCircleError
return YES;
}
return NO;
}
- (void)groupStart
{
secnotice("escrowrequest", "Attempting to escrow any pending prerecords");
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: self.error = error;
if([self.lockStateTracker isLockedError: error]) {
secnotice("escrowrequest", "Will retry after unlock");
self.nextState = EscrowRequestStateWaitForUnlock;
} else {
self.nextState = EscrowRequestStateNothingToDo;
}
return;
}
error = nil;
SecEscrowPendingRecord* record = nil;
for(SecEscrowPendingRecord* existingRecord in records) {
if(existingRecord.uploadCompleted) {
secnotice("escrowrequest", "Skipping completed escrow request ( continue;
}
if(self.enforceRateLimiting && [existingRecord escrowAttemptedWithinLastSeconds:5*60]) {
secnotice("escrowrequest", "Skipping pending escrow request ( continue;
}
if(existingRecord.hasSerializedPrerecord) {
record = existingRecord;
break;
}
}
if(record == nil && record.uuid == nil) {
secnotice("escrowrequest", "No pending escrow request has a prerecord");
self.nextState = EscrowRequestStateNothingToDo;
return;
}
secnotice("escrowrequest", "escrow request have pre-record uploading:
// Ask CDP to escrow the escrow-record. Use the "finish operation" trick to wait
CKKSResultOperation* finishOp = [CKKSResultOperation named:@"cdp-finish" withBlock:^{}];
[self dependOnBeforeGroupFinished: finishOp];
/*
* Update and save the preRecord an extra time (before we crash)
*/
record.lastEscrowAttemptTime = (uint64_t) ([[NSDate date] timeIntervalSince1970] * 1000);
record.uploadRetries += 1;
// Save the last escrow attempt time to keychain
NSError* saveError = nil;
[record saveToKeychain:&saveError];
if(saveError) {
secerror("escrowrequest: unable to save last escrow time: }
WEAKIFY(self);
[EscrowRequestPerformEscrowEnrollOperation cdpUploadPrerecord:record
secretType:CDPComplexDeviceSecretType
reply:^(BOOL didUpdate, NSError * _Nullable error) {
STRONGIFY(self);
//* check for fatal errors that definatly should make us give up
if ([self checkFatalError:error]) {
secerror("escrowrequest: fatal error for record: NSError* deleteError = nil;
[record deleteFromKeychain:&deleteError];
if(saveError) {
secerror("escrowrequest: unable to delete last escrow time: }
self.error = error;
[self.operationQueue addOperation:finishOp];
return;
}
if(error || !didUpdate) {
secerror("escrowrequest: prerecord
self.error = error;
[self.operationQueue addOperation:finishOp];
return;
}
self.numberOfRecordsUploaded = 1;
secerror("escrowrequest: prerecord
record.uploadCompleted = true;
NSError* saveError = nil;
[record saveToKeychain:&saveError];
if(saveError) {
secerror("escrowrequest: unable to save last escrow time: }
if(saveError) {
secerror("escrowrequest: unable to save completion of prerecord }
self.nextState = EscrowRequestStateNothingToDo;
[self.operationQueue addOperation:finishOp];
}];
}
+ (void)cdpUploadPrerecord:(SecEscrowPendingRecord*)recordToSend
secretType:(CDPDeviceSecretType)secretType
reply:(void (^)(BOOL didUpdate, NSError* _Nullable error))reply
{
CDPStateController *controller = [[CDPStateController alloc] initWithContext:nil];
[controller attemptToEscrowPreRecord:@"unknown-local-passcode"
preRecordUUID:recordToSend.uuid
secretType:secretType
completion:^(BOOL didUpdate, NSError *error) {
reply(didUpdate, error);
}];
}
@end