]> git.saurik.com Git - apple/security.git/blob - keychain/ot/OTClientStateMachine.m
Security-59754.41.1.tar.gz
[apple/security.git] / keychain / ot / OTClientStateMachine.m
1 /*
2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23 #if OCTAGON
24
25 #import <CoreCDP/CDPAccount.h>
26
27 #import <Security/Security.h>
28
29 #include <utilities/SecFileLocations.h>
30 #include <Security/SecRandomP.h>
31
32 #import "keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h"
33
34 #import "keychain/ckks/CKKS.h"
35 #import "keychain/ckks/CKKSAnalytics.h"
36 #import "keychain/ckks/CKKSResultOperation.h"
37
38 #import "keychain/ckks/OctagonAPSReceiver.h"
39
40 #import "keychain/ot/proto/generated_source/OTAccountMetadataClassC.h"
41 #import "keychain/ot/ObjCImprovements.h"
42
43 #import "keychain/ot/OTConstants.h"
44 #import "keychain/ot/OTClientStateMachine.h"
45 #import "keychain/ot/OTSOSAdapter.h"
46 #import "keychain/ot/OTEpochOperation.h"
47 #import "keychain/ot/OTClientVoucherOperation.h"
48 #import "keychain/ot/OTStates.h"
49
50 #import "keychain/ot/ObjCImprovements.h"
51 #import "keychain/ot/proto/generated_source/OTAccountMetadataClassC.h"
52
53 #import "keychain/categories/NSError+UsefulConstructors.h"
54 #import "keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.h"
55
56 #define otclientnotice(scope, format, ...) __extension__({ \
57 os_log(secLogObjForCFScope((__bridge CFStringRef)(scope)), format, ##__VA_ARGS__); \
58 })
59
60 /*Piggybacking and ProximitySetup as Acceptor Octagon only*/
61 OctagonState* const OctagonStateAcceptorBeginClientJoin = (OctagonState*)@"client_join";
62 OctagonState* const OctagonStateAcceptorBeginAwaitEpochRequest = (OctagonState*)@"await_epoch_request";
63 OctagonState* const OctagonStateAcceptorEpochPrepared = (OctagonState*)@"epoch_prepared";
64 OctagonState* const OctagonStateAcceptorAwaitingIdentity = (OctagonState*)@"await_identity";
65 OctagonState* const OctagonStateAcceptorVoucherPrepared = (OctagonState*)@"voucher_prepared";
66 OctagonState* const OctagonStateAcceptorDone = (OctagonState*)@"done";
67
68 NSDictionary<OctagonState*, NSNumber*>* OctagonClientStateMap(void) {
69 static NSDictionary<OctagonState*, NSNumber*>* map = nil;
70 static dispatch_once_t onceToken;
71 dispatch_once(&onceToken, ^{
72 map = @{
73 OctagonStateAcceptorBeginClientJoin: @0U,
74 OctagonStateAcceptorBeginAwaitEpochRequest: @1U,
75 OctagonStateAcceptorEpochPrepared: @2U,
76 OctagonStateAcceptorAwaitingIdentity: @3U,
77 OctagonStateAcceptorVoucherPrepared: @4U,
78 OctagonStateAcceptorDone: @5U,
79 };
80 });
81 return map;
82 }
83
84 @interface OTClientStateMachine ()
85 {
86 OctagonState* _currentState;
87 }
88
89 @property dispatch_queue_t queue;
90 @property NSOperationQueue* operationQueue;
91
92 // Make writable
93 @property OctagonState* currentState;
94 @property NSString* clientScope;
95
96 // Set this to an operation to pause the state machine in-flight
97 @property NSOperation* holdStateMachineOperation;
98
99 @property CKKSResultOperation* nextClientStateMachineCycleOperation;
100
101 @property NSMutableArray<OctagonStateTransitionRequest<CKKSResultOperation<OctagonStateTransitionOperationProtocol>*>*>* stateMachineClientRequests;
102
103 @end
104
105 @implementation OTClientStateMachine
106
107 - (instancetype)initWithContainerName:(NSString*)containerName
108 contextID:(NSString*)contextID
109 clientName:(NSString*)clientName
110 cuttlefish:(id<NSXPCProxyCreating>)cuttlefish
111 {
112 if ((self = [super init])) {
113 _containerName = containerName;
114 _clientName = clientName;
115 _contextID = contextID;
116
117 _queue = dispatch_queue_create("com.apple.security.otclientstatemachine", DISPATCH_QUEUE_SERIAL);
118 _operationQueue = [[NSOperationQueue alloc] init];
119
120 _stateConditions = [[NSMutableDictionary alloc] init];
121 [OctagonClientStateMap() enumerateKeysAndObjectsUsingBlock:^(OctagonState * _Nonnull key, NSNumber * _Nonnull obj, BOOL * _Nonnull stop) {
122 self.stateConditions[key] = [[CKKSCondition alloc] init];
123 }];
124
125 // Use the setter method to set the condition variables
126 self.currentState = OctagonStateMachineNotStarted;
127
128 _stateMachineClientRequests = [NSMutableArray array];
129 _holdStateMachineOperation = [NSBlockOperation blockOperationWithBlock:^{}];
130
131 OctagonStateTransitionOperation* beginClientJoin = [OctagonStateTransitionOperation named:@"initialize-client"
132 entering:OctagonStateAcceptorBeginClientJoin];
133 [beginClientJoin addDependency:_holdStateMachineOperation];
134 [_operationQueue addOperation:beginClientJoin];
135
136 CKKSResultOperation* startStateMachineOp = [self createOperationToFinishAttemptForClient:beginClientJoin clientName:clientName];
137 [_operationQueue addOperation:startStateMachineOp];
138
139 _cuttlefishXPCConnection = cuttlefish;
140
141 _clientScope = [NSString stringWithFormat:@"octagon-client-%@-state", clientName];
142 }
143 return self;
144 }
145
146 - (void)dealloc
147 {
148 // TODO: how to invalidate this?
149 //[self.cuttlefishXPCConnection invalidate];
150 }
151
152 - (OctagonState* _Nonnull)currentState {
153 return _currentState;
154 }
155
156 - (void)setCurrentState:(OctagonState* _Nonnull)state {
157 if((state == nil && _currentState == nil) || ([state isEqualToString:_currentState])) {
158 // No change, do nothing.
159 } else {
160 // Fixup the condition variables as part of setting this state
161 if(_currentState) {
162 self.stateConditions[_currentState] = [[CKKSCondition alloc] init];
163 }
164
165 _currentState = state;
166
167 if(state) {
168 [self.stateConditions[state] fulfill];
169 }
170 }
171 }
172
173 - (void)startOctagonStateMachine {
174 dispatch_sync(self.queue, ^{
175 if(self.holdStateMachineOperation) {
176 [self.operationQueue addOperation: self.holdStateMachineOperation];
177 self.holdStateMachineOperation = nil;
178 }
179 });
180 }
181
182 #pragma mark --- Client State Machine Machinery
183
184 - (CKKSResultOperation*)createOperationToFinishAttemptForClient:(CKKSResultOperation<OctagonStateTransitionOperationProtocol>*)attempt clientName:(NSString*)clientName
185 {
186 WEAKIFY(self);
187
188 CKKSResultOperation* followUp = [CKKSResultOperation named:@"octagon-state-follow-up" withBlock:^{
189 STRONGIFY(self);
190
191 dispatch_sync(self.queue, ^{
192 otclientnotice(self.clientScope, "Finishing state transition attempt %@", attempt);
193
194 self.currentState = attempt.nextState;
195 self.nextClientStateMachineCycleOperation = nil;
196
197 [self _onqueueStartNextClientStateMachineOperation:clientName];
198 });
199 }];
200 [followUp addNullableDependency:self.holdStateMachineOperation];
201 [followUp addNullableDependency:attempt];
202 return followUp;
203 }
204
205 - (void)_onqueuePokeClientStateMachine:(NSString*)clientName
206 {
207 dispatch_assert_queue(self.queue);
208 if(!self.nextClientStateMachineCycleOperation) {
209 [self _onqueueStartNextClientStateMachineOperation:clientName];
210 }
211 }
212
213 - (void)_onqueueStartNextClientStateMachineOperation:(NSString*)clientName {
214 dispatch_assert_queue(self.queue);
215
216 CKKSResultOperation<OctagonStateTransitionOperationProtocol>* nextAttempt = [self _onqueueNextClientStateMachineTransition:clientName];
217 if(nextAttempt) {
218 otclientnotice(self.clientScope, "Beginning client state transition attempt %@", nextAttempt);
219
220 self.nextClientStateMachineCycleOperation = [self createOperationToFinishAttemptForClient:nextAttempt clientName:clientName];
221 [self.operationQueue addOperation:self.nextClientStateMachineCycleOperation];
222
223 [nextAttempt addNullableDependency:self.holdStateMachineOperation];
224 [self.operationQueue addOperation:nextAttempt];
225 }
226 }
227
228 - (void)handleExternalClientStateMachineRequest:(OctagonStateTransitionRequest<CKKSResultOperation<OctagonStateTransitionOperationProtocol>*>*)request client:(NSString*)clientName
229 {
230 dispatch_sync(self.queue, ^{
231 [self.stateMachineClientRequests addObject:request];
232
233 [self _onqueuePokeClientStateMachine:clientName];
234 });
235 }
236 -(BOOL) isAcceptorWaitingForFirstMessage
237 {
238 BOOL isWaitingForFirstMessage = NO;
239
240 if([self.currentState isEqualToString:OctagonStateAcceptorBeginClientJoin] || [self.currentState isEqualToString:OctagonStateMachineNotStarted]){
241 isWaitingForFirstMessage = YES;
242 }
243 return isWaitingForFirstMessage;
244 }
245 #pragma mark --- Client State Machine Transitions
246 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)_onqueueNextClientStateMachineTransition:(NSString*)clientName
247 {
248 dispatch_assert_queue(self.queue);
249
250 // Check requests: do any of them want to come from this state?
251 for(OctagonStateTransitionRequest<OctagonStateTransitionOperation*>* request in self.stateMachineClientRequests) {
252 if([request.sourceStates containsObject:self.currentState]) {
253 CKKSResultOperation<OctagonStateTransitionOperationProtocol>* attempt = [request _onqueueStart];
254
255 if(attempt) {
256 otclientnotice(self.clientScope, "Running client %@ state machine request %@ (from %@)", clientName, request, self.currentState);
257 return attempt;
258 }
259 }
260 }
261 if([self.currentState isEqualToString: OctagonStateAcceptorVoucherPrepared]){
262 return [OctagonStateTransitionOperation named:@"octagon-voucher-prepared"
263 intending:OctagonStateAcceptorDone
264 errorState:OctagonStateError
265 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
266 otclientnotice(self.clientScope, "moving to state done for %@", clientName);
267 op.nextState = OctagonStateAcceptorDone;
268 }];
269 }else if([self.currentState isEqualToString: OctagonStateAcceptorDone]){
270 otclientnotice(self.clientScope, "removing client connection for %@", clientName);
271 }
272
273 return nil;
274 }
275
276 - (void)notifyContainerChange
277 {
278 secerror("OTCuttlefishContext: received a cuttlefish push notification (%@)", self.containerName);
279 [[self.cuttlefishXPCConnection synchronousRemoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
280 secerror("octagon: Can't talk with TrustedPeersHelper, update is lost: %@", error);
281
282 }] updateWithContainer:self.containerName
283 context:self.contextID
284 deviceName:nil
285 serialNumber:nil
286 osVersion:nil
287 policyVersion:nil
288 policySecrets:nil
289 syncUserControllableViews:nil
290 reply:^(TrustedPeersHelperPeerState* peerState, TPSyncingPolicy* policy, NSError* error) {
291 if(error) {
292 secerror("OTCuttlefishContext: updating errored: %@", error);
293 } else {
294 secerror("OTCuttlefishContext: update complete");
295 }
296 }];
297 }
298
299 #pragma mark --- External Interfaces
300
301 - (void)rpcEpoch:(OTCuttlefishContext*)cuttlefishContext
302 reply:(void (^)(uint64_t epoch,
303 NSError * _Nullable error))reply
304 {
305 OTEpochOperation* pendingOp = [[OTEpochOperation alloc] init:self.containerName
306 contextID:self.contextID
307 intendedState:OctagonStateAcceptorAwaitingIdentity
308 errorState:OctagonStateAcceptorDone
309 cuttlefishXPCWrapper:cuttlefishContext.cuttlefishXPCWrapper];
310
311 OctagonStateTransitionRequest<OTEpochOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"rpcEpoch"
312 sourceStates:[NSSet setWithArray:@[OctagonStateAcceptorBeginClientJoin]]
313 serialQueue:self.queue
314 timeout:2*NSEC_PER_SEC
315 transitionOp:pendingOp];
316
317 CKKSResultOperation* callback = [CKKSResultOperation named:@"rpcEpoch-callback"
318 withBlock:^{
319 secnotice("otrpc", "Returning an epoch call: %llu %@", pendingOp.epoch, pendingOp.error);
320 reply(pendingOp.epoch,
321 pendingOp.error);
322 }];
323 [callback addDependency:pendingOp];
324 [self.operationQueue addOperation: callback];
325
326 [self handleExternalClientStateMachineRequest:request client:self.clientName];
327
328 return;
329 }
330
331 - (void)rpcVoucher:(OTCuttlefishContext*)cuttlefishContext
332 peerID:(NSString*)peerID
333 permanentInfo:(NSData *)permanentInfo
334 permanentInfoSig:(NSData *)permanentInfoSig
335 stableInfo:(NSData *)stableInfo
336 stableInfoSig:(NSData *)stableInfoSig
337 reply:(void (^)(NSData* voucher, NSData* voucherSig, NSError * _Nullable error))reply
338 {
339 OTClientVoucherOperation* pendingOp = [[OTClientVoucherOperation alloc] initWithDependencies:cuttlefishContext.operationDependencies
340 intendedState:OctagonStateAcceptorVoucherPrepared
341 errorState:OctagonStateAcceptorDone
342 deviceInfo:[cuttlefishContext prepareInformation]
343 peerID:peerID
344 permanentInfo:permanentInfo
345 permanentInfoSig:permanentInfoSig
346 stableInfo:stableInfo
347 stableInfoSig:stableInfoSig];
348
349 OctagonStateTransitionRequest<OTClientVoucherOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"rpcVoucher"
350 sourceStates:[NSSet setWithArray:@[OctagonStateAcceptorAwaitingIdentity]]
351 serialQueue:self.queue
352 timeout:2*NSEC_PER_SEC
353
354 transitionOp:pendingOp];
355 CKKSResultOperation* callback = [CKKSResultOperation named:@"rpcVoucher-callback"
356 withBlock:^{
357 secnotice("otrpc", "Returning a voucher call: %@, %@, %@", pendingOp.voucher, pendingOp.voucherSig, pendingOp.error);
358 reply(pendingOp.voucher, pendingOp.voucherSig, pendingOp.error);
359 }];
360 [callback addDependency:pendingOp];
361 [self.operationQueue addOperation: callback];
362
363 [self handleExternalClientStateMachineRequest:request client:self.clientName];
364
365 return;
366 }
367
368 @end
369 #endif