]> git.saurik.com Git - apple/security.git/blob - keychain/escrowrequest/EscrowRequestController.m
Security-59306.11.20.tar.gz
[apple/security.git] / keychain / escrowrequest / EscrowRequestController.m
1
2 #import "utilities/debugging.h"
3
4 #import "keychain/ot/OctagonStateMachine.h"
5 #import "keychain/ot/OTStates.h"
6 #import "keychain/escrowrequest/EscrowRequestController.h"
7 #import "keychain/escrowrequest/EscrowRequestServer.h"
8
9 #import "keychain/ckks/CKKSLockStateTracker.h"
10
11 #import "keychain/ot/ObjCImprovements.h"
12
13 #import "keychain/escrowrequest/operations/EscrowRequestInformCloudServicesOperation.h"
14 #import "keychain/escrowrequest/operations/EscrowRequestPerformEscrowEnrollOperation.h"
15
16 #import "keychain/escrowrequest/generated_source/SecEscrowPendingRecord.h"
17 #import "keychain/escrowrequest/SecEscrowPendingRecord+KeychainSupport.h"
18
19 OctagonState* const EscrowRequestStateNothingToDo = (OctagonState*)@"nothing_to_do";
20 OctagonState* const EscrowRequestStateTriggerCloudServices = (OctagonState*)@"trigger_cloudservices";
21
22 OctagonState* const EscrowRequestStateAttemptEscrowUpload = (OctagonState*)@"trigger_escrow_upload";
23 OctagonState* const EscrowRequestStateWaitForUnlock = (OctagonState*)@"wait_for_unlock";
24
25 @interface EscrowRequestController ()
26 @property dispatch_queue_t queue;
27 @property CKKSLockStateTracker* lockStateTracker;
28 @property bool haveRecordedDate;
29 @end
30
31 @implementation EscrowRequestController
32
33 - (instancetype)initWithLockStateTracker:(CKKSLockStateTracker*)lockStateTracker
34 {
35 if((self = [super init])) {
36 _queue = dispatch_queue_create("EscrowRequestControllerQueue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
37 _lockStateTracker = lockStateTracker;
38
39 _stateMachine = [[OctagonStateMachine alloc] initWithName:@"escrowrequest"
40 states:[NSSet setWithArray:@[EscrowRequestStateNothingToDo,
41 EscrowRequestStateTriggerCloudServices,
42 EscrowRequestStateAttemptEscrowUpload,
43 EscrowRequestStateWaitForUnlock]]
44 initialState:EscrowRequestStateNothingToDo
45 queue:_queue
46 stateEngine:self
47 lockStateTracker:lockStateTracker];
48
49 _forceIgnoreCloudServicesRateLimiting = false;
50 }
51
52 return self;
53 }
54
55 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol> * _Nullable)_onqueueNextStateMachineTransition:(nonnull OctagonState *)currentState
56 flags:(nonnull OctagonFlags *)flags
57 pendingFlags:(nonnull id<OctagonStateOnqueuePendingFlagHandler>)pendingFlagHandler
58 {
59 if([flags _onqueueContains:OctagonFlagEscrowRequestInformCloudServicesOperation]) {
60 [flags _onqueueRemoveFlag:OctagonFlagEscrowRequestInformCloudServicesOperation];
61 return [[EscrowRequestInformCloudServicesOperation alloc] initWithIntendedState:EscrowRequestStateNothingToDo
62 errorState:EscrowRequestStateNothingToDo
63 lockStateTracker:self.lockStateTracker];
64 }
65
66 if([currentState isEqualToString:EscrowRequestStateTriggerCloudServices]) {
67 return [[EscrowRequestInformCloudServicesOperation alloc] initWithIntendedState:EscrowRequestStateNothingToDo
68 errorState:EscrowRequestStateNothingToDo
69 lockStateTracker:self.lockStateTracker];
70 }
71
72 if([currentState isEqualToString:EscrowRequestStateAttemptEscrowUpload]) {
73 return [[EscrowRequestPerformEscrowEnrollOperation alloc] initWithIntendedState:EscrowRequestStateNothingToDo
74 errorState:EscrowRequestStateNothingToDo
75 enforceRateLimiting:true
76 lockStateTracker:self.lockStateTracker];
77 }
78
79 if([currentState isEqualToString:EscrowRequestStateWaitForUnlock]) {
80 secnotice("escrowrequest", "waiting for unlock before continuing with state machine");
81 OctagonStateTransitionOperation* op = [OctagonStateTransitionOperation named:@"wait-for-unlock"
82 entering:EscrowRequestStateNothingToDo];
83 [op addNullableDependency:self.lockStateTracker.unlockDependency];
84 return op;
85 }
86
87 NSError* error = nil;
88 NSArray<SecEscrowPendingRecord*>* records = [SecEscrowPendingRecord loadAllFromKeychain:&error];
89 if(error) {
90 if([self.lockStateTracker isLockedError:error]) {
91 return [OctagonStateTransitionOperation named:@"wait-for-unlock"
92 entering:EscrowRequestStateWaitForUnlock];
93 }
94 secnotice("escrowrequest", "failed to fetch records from keychain, nothing to do: %@", error);
95 return nil;
96 }
97
98 // First, do we need to poke CloudServices?
99 for(SecEscrowPendingRecord* record in records) {
100 // Completed records don't need anything.
101 if(record.hasUploadCompleted && record.uploadCompleted) {
102 continue;
103 }
104
105 if (!self.haveRecordedDate) {
106 NSDate *date = [[CKKSAnalytics logger] datePropertyForKey:ESRPendingSince];
107 if (date == NULL) {
108 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:ESRPendingSince];
109 }
110 self.haveRecordedDate = true;
111 }
112
113 uint64_t fiveMinutesAgo = ((uint64_t)[[NSDate date] timeIntervalSince1970] * 1000) - (1000*60*5);
114
115 if(!record.certCached) {
116 if(!self.forceIgnoreCloudServicesRateLimiting && (record.hasLastCloudServicesTriggerTime && record.lastCloudServicesTriggerTime >= fiveMinutesAgo)) {
117 secnotice("escrowrequest", "Request %@ needs to cache a certificate, but that has been attempted recently. Holding off...", record.uuid);
118 continue;
119 }
120
121 secnotice("escrowrequest", "Request %@ needs a cached certififcate", record.uuid);
122
123 return [OctagonStateTransitionOperation named:@"escrow-request-cache-cert"
124 entering:EscrowRequestStateTriggerCloudServices];
125 }
126
127 if(record.hasSerializedPrerecord) {
128 if([record escrowAttemptedWithinLastSeconds:5*60]) {
129 secnotice("escrowrequest", "Request %@ needs to be stored, but has been attempted recently. Holding off...", record.uuid);
130 continue;
131 }
132
133 secnotice("escrowrequest", "Request %@ needs to be stored!", record.uuid);
134
135 return [OctagonStateTransitionOperation named:@"escrow-request-attempt-escrow-upload"
136 entering:EscrowRequestStateAttemptEscrowUpload];
137 }
138 }
139
140
141 return nil;
142 }
143
144 - (void)triggerEscrowUpdateRPC:(nonnull NSString *)reason
145 reply:(nonnull void (^)(NSError * _Nullable))reply
146 {
147 [self.stateMachine startOperation];
148
149 NSError* error = nil;
150 NSArray<SecEscrowPendingRecord*>* records = [SecEscrowPendingRecord loadAllFromKeychain:&error];
151 if(error && !([error.domain isEqualToString:NSOSStatusErrorDomain] && error.code == errSecItemNotFound)) {
152 secnotice("escrowrequest", "failed to fetch records from keychain: %@", error);
153 reply(error);
154 return;
155 }
156 error = nil;
157
158 secnotice("escrowrequest", "Investigating a new escrow request");
159
160 BOOL escrowRequestExists = NO;
161 for(SecEscrowPendingRecord* existingRecord in records) {
162 if(existingRecord.uploadCompleted) {
163 continue;
164 }
165
166 if (existingRecord.hasAltDSID) {
167 continue;
168 }
169
170 secnotice("escrowrequest", "Retriggering an existing escrow request: %@", existingRecord);
171 existingRecord.hasCertCached = false;
172 existingRecord.serializedPrerecord = nil;
173
174 [existingRecord saveToKeychain:&error];
175 if(error) {
176 secerror("escrowrequest: Unable to save modified request to keychain: %@", error);
177 reply(error);
178 return;
179 }
180
181 secnotice("escrowrequest", "Retriggering an existing escrow request complete");
182 escrowRequestExists = YES;
183 }
184
185 if(escrowRequestExists == NO){
186 secnotice("escrowrequest", "Creating a new escrow request");
187
188 SecEscrowPendingRecord* record = [[SecEscrowPendingRecord alloc] init];
189 record.uuid = [[NSUUID UUID] UUIDString];
190 record.altDSID = nil;
191 record.triggerRequestTime = ((uint64_t)[[NSDate date] timeIntervalSince1970] * 1000);
192 secnotice("escrowrequest", "beginning a new escrow request (%@)", record.uuid);
193
194 [record saveToKeychain:&error];
195
196 if(error) {
197 secerror("escrowrequest: unable to save escrow update request: %@", error);
198 reply(error);
199 return;
200 }
201 }
202
203 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:ESRPendingSince];
204 self.haveRecordedDate = true;
205
206 [self.stateMachine handleFlag:OctagonFlagEscrowRequestInformCloudServicesOperation];
207
208 reply(nil);
209 }
210
211 - (void)storePrerecordsInEscrowRPC:(void (^)(uint64_t count, NSError* _Nullable error))reply
212 {
213 EscrowRequestPerformEscrowEnrollOperation* op = [[EscrowRequestPerformEscrowEnrollOperation alloc] initWithIntendedState:EscrowRequestStateNothingToDo
214 errorState:EscrowRequestStateNothingToDo
215 enforceRateLimiting:false
216 lockStateTracker:self.lockStateTracker];
217 [self.stateMachine startOperation];
218 [self.stateMachine doSimpleStateMachineRPC:@"trigger-escrow-store"
219 op:op
220 sourceStates:[NSSet setWithObject:EscrowRequestStateNothingToDo]
221 reply:^(NSError * _Nullable error) {
222 secnotice("escrowrequest", "Uploaded %d records with error %@", (int)op.numberOfRecordsUploaded, error);
223 reply(op.numberOfRecordsUploaded, error);
224 }];
225 }
226
227 @end