]>
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, | |
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 |