]>
Commit | Line | Data |
---|---|---|
b54c578e A |
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, | |
805875f8 A |
41 | EscrowRequestStateTriggerCloudServices, |
42 | EscrowRequestStateAttemptEscrowUpload, | |
43 | EscrowRequestStateWaitForUnlock]] | |
44 | flags: [NSSet setWithArray:@[OctagonFlagEscrowRequestInformCloudServicesOperation]] | |
b54c578e A |
45 | initialState:EscrowRequestStateNothingToDo |
46 | queue:_queue | |
47 | stateEngine:self | |
48 | lockStateTracker:lockStateTracker]; | |
49 | ||
50 | _forceIgnoreCloudServicesRateLimiting = false; | |
51 | } | |
52 | ||
53 | return self; | |
54 | } | |
55 | ||
56 | - (CKKSResultOperation<OctagonStateTransitionOperationProtocol> * _Nullable)_onqueueNextStateMachineTransition:(nonnull OctagonState *)currentState | |
57 | flags:(nonnull OctagonFlags *)flags | |
58 | pendingFlags:(nonnull id<OctagonStateOnqueuePendingFlagHandler>)pendingFlagHandler | |
59 | { | |
d64be36e | 60 | dispatch_assert_queue(self.queue); |
b54c578e A |
61 | if([flags _onqueueContains:OctagonFlagEscrowRequestInformCloudServicesOperation]) { |
62 | [flags _onqueueRemoveFlag:OctagonFlagEscrowRequestInformCloudServicesOperation]; | |
63 | return [[EscrowRequestInformCloudServicesOperation alloc] initWithIntendedState:EscrowRequestStateNothingToDo | |
64 | errorState:EscrowRequestStateNothingToDo | |
65 | lockStateTracker:self.lockStateTracker]; | |
66 | } | |
67 | ||
68 | if([currentState isEqualToString:EscrowRequestStateTriggerCloudServices]) { | |
69 | return [[EscrowRequestInformCloudServicesOperation alloc] initWithIntendedState:EscrowRequestStateNothingToDo | |
70 | errorState:EscrowRequestStateNothingToDo | |
71 | lockStateTracker:self.lockStateTracker]; | |
72 | } | |
73 | ||
74 | if([currentState isEqualToString:EscrowRequestStateAttemptEscrowUpload]) { | |
75 | return [[EscrowRequestPerformEscrowEnrollOperation alloc] initWithIntendedState:EscrowRequestStateNothingToDo | |
76 | errorState:EscrowRequestStateNothingToDo | |
77 | enforceRateLimiting:true | |
78 | lockStateTracker:self.lockStateTracker]; | |
79 | } | |
80 | ||
81 | if([currentState isEqualToString:EscrowRequestStateWaitForUnlock]) { | |
82 | secnotice("escrowrequest", "waiting for unlock before continuing with state machine"); | |
83 | OctagonStateTransitionOperation* op = [OctagonStateTransitionOperation named:@"wait-for-unlock" | |
84 | entering:EscrowRequestStateNothingToDo]; | |
85 | [op addNullableDependency:self.lockStateTracker.unlockDependency]; | |
86 | return op; | |
87 | } | |
88 | ||
89 | NSError* error = nil; | |
90 | NSArray<SecEscrowPendingRecord*>* records = [SecEscrowPendingRecord loadAllFromKeychain:&error]; | |
91 | if(error) { | |
92 | if([self.lockStateTracker isLockedError:error]) { | |
93 | return [OctagonStateTransitionOperation named:@"wait-for-unlock" | |
94 | entering:EscrowRequestStateWaitForUnlock]; | |
95 | } | |
96 | secnotice("escrowrequest", "failed to fetch records from keychain, nothing to do: %@", error); | |
97 | return nil; | |
98 | } | |
99 | ||
100 | // First, do we need to poke CloudServices? | |
101 | for(SecEscrowPendingRecord* record in records) { | |
102 | // Completed records don't need anything. | |
103 | if(record.hasUploadCompleted && record.uploadCompleted) { | |
104 | continue; | |
105 | } | |
106 | ||
107 | if (!self.haveRecordedDate) { | |
108 | NSDate *date = [[CKKSAnalytics logger] datePropertyForKey:ESRPendingSince]; | |
109 | if (date == NULL) { | |
110 | [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:ESRPendingSince]; | |
111 | } | |
112 | self.haveRecordedDate = true; | |
113 | } | |
114 | ||
115 | uint64_t fiveMinutesAgo = ((uint64_t)[[NSDate date] timeIntervalSince1970] * 1000) - (1000*60*5); | |
116 | ||
117 | if(!record.certCached) { | |
118 | if(!self.forceIgnoreCloudServicesRateLimiting && (record.hasLastCloudServicesTriggerTime && record.lastCloudServicesTriggerTime >= fiveMinutesAgo)) { | |
119 | secnotice("escrowrequest", "Request %@ needs to cache a certificate, but that has been attempted recently. Holding off...", record.uuid); | |
120 | continue; | |
121 | } | |
122 | ||
123 | secnotice("escrowrequest", "Request %@ needs a cached certififcate", record.uuid); | |
124 | ||
125 | return [OctagonStateTransitionOperation named:@"escrow-request-cache-cert" | |
126 | entering:EscrowRequestStateTriggerCloudServices]; | |
127 | } | |
128 | ||
129 | if(record.hasSerializedPrerecord) { | |
130 | if([record escrowAttemptedWithinLastSeconds:5*60]) { | |
131 | secnotice("escrowrequest", "Request %@ needs to be stored, but has been attempted recently. Holding off...", record.uuid); | |
132 | continue; | |
133 | } | |
134 | ||
135 | secnotice("escrowrequest", "Request %@ needs to be stored!", record.uuid); | |
136 | ||
137 | return [OctagonStateTransitionOperation named:@"escrow-request-attempt-escrow-upload" | |
138 | entering:EscrowRequestStateAttemptEscrowUpload]; | |
139 | } | |
140 | } | |
141 | ||
142 | ||
143 | return nil; | |
144 | } | |
145 | ||
146 | - (void)triggerEscrowUpdateRPC:(nonnull NSString *)reason | |
147 | reply:(nonnull void (^)(NSError * _Nullable))reply | |
148 | { | |
149 | [self.stateMachine startOperation]; | |
150 | ||
151 | NSError* error = nil; | |
152 | NSArray<SecEscrowPendingRecord*>* records = [SecEscrowPendingRecord loadAllFromKeychain:&error]; | |
153 | if(error && !([error.domain isEqualToString:NSOSStatusErrorDomain] && error.code == errSecItemNotFound)) { | |
154 | secnotice("escrowrequest", "failed to fetch records from keychain: %@", error); | |
155 | reply(error); | |
156 | return; | |
157 | } | |
158 | error = nil; | |
159 | ||
160 | secnotice("escrowrequest", "Investigating a new escrow request"); | |
161 | ||
162 | BOOL escrowRequestExists = NO; | |
163 | for(SecEscrowPendingRecord* existingRecord in records) { | |
164 | if(existingRecord.uploadCompleted) { | |
165 | continue; | |
166 | } | |
167 | ||
168 | if (existingRecord.hasAltDSID) { | |
169 | continue; | |
170 | } | |
171 | ||
172 | secnotice("escrowrequest", "Retriggering an existing escrow request: %@", existingRecord); | |
173 | existingRecord.hasCertCached = false; | |
174 | existingRecord.serializedPrerecord = nil; | |
175 | ||
176 | [existingRecord saveToKeychain:&error]; | |
177 | if(error) { | |
178 | secerror("escrowrequest: Unable to save modified request to keychain: %@", error); | |
179 | reply(error); | |
180 | return; | |
181 | } | |
182 | ||
183 | secnotice("escrowrequest", "Retriggering an existing escrow request complete"); | |
184 | escrowRequestExists = YES; | |
185 | } | |
186 | ||
187 | if(escrowRequestExists == NO){ | |
188 | secnotice("escrowrequest", "Creating a new escrow request"); | |
189 | ||
190 | SecEscrowPendingRecord* record = [[SecEscrowPendingRecord alloc] init]; | |
191 | record.uuid = [[NSUUID UUID] UUIDString]; | |
192 | record.altDSID = nil; | |
193 | record.triggerRequestTime = ((uint64_t)[[NSDate date] timeIntervalSince1970] * 1000); | |
194 | secnotice("escrowrequest", "beginning a new escrow request (%@)", record.uuid); | |
195 | ||
196 | [record saveToKeychain:&error]; | |
197 | ||
198 | if(error) { | |
199 | secerror("escrowrequest: unable to save escrow update request: %@", error); | |
200 | reply(error); | |
201 | return; | |
202 | } | |
203 | } | |
204 | ||
205 | [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:ESRPendingSince]; | |
206 | self.haveRecordedDate = true; | |
207 | ||
208 | [self.stateMachine handleFlag:OctagonFlagEscrowRequestInformCloudServicesOperation]; | |
209 | ||
210 | reply(nil); | |
211 | } | |
212 | ||
213 | - (void)storePrerecordsInEscrowRPC:(void (^)(uint64_t count, NSError* _Nullable error))reply | |
214 | { | |
215 | EscrowRequestPerformEscrowEnrollOperation* op = [[EscrowRequestPerformEscrowEnrollOperation alloc] initWithIntendedState:EscrowRequestStateNothingToDo | |
216 | errorState:EscrowRequestStateNothingToDo | |
217 | enforceRateLimiting:false | |
218 | lockStateTracker:self.lockStateTracker]; | |
219 | [self.stateMachine startOperation]; | |
220 | [self.stateMachine doSimpleStateMachineRPC:@"trigger-escrow-store" | |
221 | op:op | |
222 | sourceStates:[NSSet setWithObject:EscrowRequestStateNothingToDo] | |
223 | reply:^(NSError * _Nullable error) { | |
224 | secnotice("escrowrequest", "Uploaded %d records with error %@", (int)op.numberOfRecordsUploaded, error); | |
225 | reply(op.numberOfRecordsUploaded, error); | |
226 | }]; | |
227 | } | |
228 | ||
229 | @end |