]> git.saurik.com Git - apple/security.git/blob - keychain/escrowrequest/EscrowRequestController.m
Security-59754.41.1.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 flags: [NSSet setWithArray:@[OctagonFlagEscrowRequestInformCloudServicesOperation]]
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 {
60 dispatch_assert_queue(self.queue);
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