2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
25 #import <CoreCDP/CDPAccount.h>
27 #import <Security/Security.h>
29 #include <utilities/SecFileLocations.h>
30 #include <Security/SecRandomP.h>
32 #import "keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h"
34 #import "keychain/ckks/CKKS.h"
35 #import "keychain/ckks/CKKSAnalytics.h"
36 #import "keychain/ckks/CKKSResultOperation.h"
38 #import "keychain/ckks/OctagonAPSReceiver.h"
40 #import "keychain/ot/proto/generated_source/OTAccountMetadataClassC.h"
41 #import "keychain/ot/ObjCImprovements.h"
43 #import "keychain/ot/OTConstants.h"
44 #import "keychain/ot/OTClientStateMachine.h"
45 #import "keychain/ot/OTPrepareOperation.h"
46 #import "keychain/ot/OTSOSAdapter.h"
47 #import "keychain/ot/OTEpochOperation.h"
48 #import "keychain/ot/OTClientVoucherOperation.h"
49 #import "keychain/ot/OTStates.h"
51 #import "keychain/ot/ObjCImprovements.h"
52 #import "keychain/ot/proto/generated_source/OTAccountMetadataClassC.h"
54 #import "keychain/categories/NSError+UsefulConstructors.h"
55 #import "keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.h"
57 #define otclientnotice(scope, format, ...) __extension__({ \
58 os_log(secLogObjForCFScope((__bridge CFStringRef)(scope)), format, ##__VA_ARGS__); \
61 /*Piggybacking and ProximitySetup as Acceptor Octagon only*/
62 OctagonState* const OctagonStateAcceptorBeginClientJoin = (OctagonState*)@"client_join";
63 OctagonState* const OctagonStateAcceptorBeginAwaitEpochRequest = (OctagonState*)@"await_epoch_request";
64 OctagonState* const OctagonStateAcceptorEpochPrepared = (OctagonState*)@"epoch_prepared";
65 OctagonState* const OctagonStateAcceptorAwaitingIdentity = (OctagonState*)@"await_identity";
66 OctagonState* const OctagonStateAcceptorVoucherPrepared = (OctagonState*)@"voucher_prepared";
67 OctagonState* const OctagonStateAcceptorDone = (OctagonState*)@"done";
69 NSDictionary<OctagonState*, NSNumber*>* OctagonClientStateMap(void) {
70 static NSDictionary<OctagonState*, NSNumber*>* map = nil;
71 static dispatch_once_t onceToken;
72 dispatch_once(&onceToken, ^{
74 OctagonStateAcceptorBeginClientJoin: @0U,
75 OctagonStateAcceptorBeginAwaitEpochRequest: @1U,
76 OctagonStateAcceptorEpochPrepared: @2U,
77 OctagonStateAcceptorAwaitingIdentity: @3U,
78 OctagonStateAcceptorVoucherPrepared: @4U,
79 OctagonStateAcceptorDone: @5U,
85 @interface OTClientStateMachine ()
87 OctagonState* _currentState;
90 @property dispatch_queue_t queue;
91 @property NSOperationQueue* operationQueue;
94 @property OctagonState* currentState;
95 @property NSString* clientScope;
97 // Set this to an operation to pause the state machine in-flight
98 @property NSOperation* holdStateMachineOperation;
100 @property CKKSResultOperation* nextClientStateMachineCycleOperation;
102 @property NSMutableArray<OctagonStateTransitionRequest<CKKSResultOperation<OctagonStateTransitionOperationProtocol>*>*>* stateMachineClientRequests;
106 @implementation OTClientStateMachine
108 - (instancetype)initWithContainerName:(NSString*)containerName
109 contextID:(NSString*)contextID
110 clientName:(NSString*)clientName
111 cuttlefish:(id<NSXPCProxyCreating>)cuttlefish
113 if ((self = [super init])) {
114 _containerName = containerName;
115 _clientName = clientName;
116 _contextID = contextID;
118 _queue = dispatch_queue_create("com.apple.security.otclientstatemachine", DISPATCH_QUEUE_SERIAL);
119 _operationQueue = [[NSOperationQueue alloc] init];
121 _stateConditions = [[NSMutableDictionary alloc] init];
122 [OctagonClientStateMap() enumerateKeysAndObjectsUsingBlock:^(OctagonState * _Nonnull key, NSNumber * _Nonnull obj, BOOL * _Nonnull stop) {
123 self.stateConditions[key] = [[CKKSCondition alloc] init];
126 // Use the setter method to set the condition variables
127 self.currentState = OctagonStateMachineNotStarted;
129 _stateMachineClientRequests = [NSMutableArray array];
130 _holdStateMachineOperation = [NSBlockOperation blockOperationWithBlock:^{}];
132 OctagonStateTransitionOperation* beginClientJoin = [OctagonStateTransitionOperation named:@"initialize-client"
133 entering:OctagonStateAcceptorBeginClientJoin];
134 [beginClientJoin addDependency:_holdStateMachineOperation];
135 [_operationQueue addOperation:beginClientJoin];
137 CKKSResultOperation* startStateMachineOp = [self createOperationToFinishAttemptForClient:beginClientJoin clientName:clientName];
138 [_operationQueue addOperation:startStateMachineOp];
140 _cuttlefishXPCConnection = cuttlefish;
142 _clientScope = [NSString stringWithFormat:@"octagon-client-%@-state", clientName];
149 // TODO: how to invalidate this?
150 //[self.cuttlefishXPCConnection invalidate];
153 - (OctagonState* _Nonnull)currentState {
154 return _currentState;
157 - (void)setCurrentState:(OctagonState* _Nonnull)state {
158 if((state == nil && _currentState == nil) || ([state isEqualToString:_currentState])) {
159 // No change, do nothing.
161 // Fixup the condition variables as part of setting this state
163 self.stateConditions[_currentState] = [[CKKSCondition alloc] init];
166 _currentState = state;
169 [self.stateConditions[state] fulfill];
174 - (void)startOctagonStateMachine {
175 dispatch_sync(self.queue, ^{
176 if(self.holdStateMachineOperation) {
177 [self.operationQueue addOperation: self.holdStateMachineOperation];
178 self.holdStateMachineOperation = nil;
183 #pragma mark --- Client State Machine Machinery
185 - (CKKSResultOperation*)createOperationToFinishAttemptForClient:(CKKSResultOperation<OctagonStateTransitionOperationProtocol>*)attempt clientName:(NSString*)clientName
189 CKKSResultOperation* followUp = [CKKSResultOperation named:@"octagon-state-follow-up" withBlock:^{
192 dispatch_sync(self.queue, ^{
193 otclientnotice(self.clientScope, "Finishing state transition attempt %@", attempt);
195 self.currentState = attempt.nextState;
196 self.nextClientStateMachineCycleOperation = nil;
198 [self _onqueueStartNextClientStateMachineOperation:clientName];
201 [followUp addNullableDependency:self.holdStateMachineOperation];
202 [followUp addNullableDependency:attempt];
206 - (void)_onqueuePokeClientStateMachine:(NSString*)clientName
208 dispatch_assert_queue(self.queue);
209 if(!self.nextClientStateMachineCycleOperation) {
210 [self _onqueueStartNextClientStateMachineOperation:clientName];
214 - (void)_onqueueStartNextClientStateMachineOperation:(NSString*)clientName {
215 dispatch_assert_queue(self.queue);
217 CKKSResultOperation<OctagonStateTransitionOperationProtocol>* nextAttempt = [self _onqueueNextClientStateMachineTransition:clientName];
219 otclientnotice(self.clientScope, "Beginning client state transition attempt %@", nextAttempt);
221 self.nextClientStateMachineCycleOperation = [self createOperationToFinishAttemptForClient:nextAttempt clientName:clientName];
222 [self.operationQueue addOperation:self.nextClientStateMachineCycleOperation];
224 [nextAttempt addNullableDependency:self.holdStateMachineOperation];
225 [self.operationQueue addOperation:nextAttempt];
229 - (void)handleExternalClientStateMachineRequest:(OctagonStateTransitionRequest<CKKSResultOperation<OctagonStateTransitionOperationProtocol>*>*)request client:(NSString*)clientName
231 dispatch_sync(self.queue, ^{
232 [self.stateMachineClientRequests addObject:request];
234 [self _onqueuePokeClientStateMachine:clientName];
237 -(BOOL) isAcceptorWaitingForFirstMessage
239 BOOL isWaitingForFirstMessage = NO;
241 if([self.currentState isEqualToString:OctagonStateAcceptorBeginClientJoin] || [self.currentState isEqualToString:OctagonStateMachineNotStarted]){
242 isWaitingForFirstMessage = YES;
244 return isWaitingForFirstMessage;
246 #pragma mark --- Client State Machine Transitions
247 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)_onqueueNextClientStateMachineTransition:(NSString*)clientName
249 dispatch_assert_queue(self.queue);
251 // Check requests: do any of them want to come from this state?
252 for(OctagonStateTransitionRequest<OctagonStateTransitionOperation*>* request in self.stateMachineClientRequests) {
253 if([request.sourceStates containsObject:self.currentState]) {
254 CKKSResultOperation<OctagonStateTransitionOperationProtocol>* attempt = [request _onqueueStart];
257 otclientnotice(self.clientScope, "Running client %@ state machine request %@ (from %@)", clientName, request, self.currentState);
262 if([self.currentState isEqualToString: OctagonStateAcceptorVoucherPrepared]){
263 return [OctagonStateTransitionOperation named:@"octagon-voucher-prepared"
264 intending:OctagonStateAcceptorDone
265 errorState:OctagonStateError
266 timeout:10*NSEC_PER_SEC
267 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
268 otclientnotice(self.clientScope, "moving to state done for %@", clientName);
269 op.nextState = OctagonStateAcceptorDone;
271 }else if([self.currentState isEqualToString: OctagonStateAcceptorDone]){
272 otclientnotice(self.clientScope, "removing client connection for %@", clientName);
278 - (void)notifyContainerChange
280 secerror("OTCuttlefishContext: received a cuttlefish push notification (%@)", self.containerName);
281 [[self.cuttlefishXPCConnection synchronousRemoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
282 secerror("octagon: Can't talk with TrustedPeersHelper, update is lost: %@", error);
284 }] updateWithContainer:self.containerName
285 context:self.contextID
291 reply:^(TrustedPeersHelperPeerState* peerState, NSError* error) {
293 secerror("OTCuttlefishContext: updating errored: %@", error);
295 secerror("OTCuttlefishContext: update complete");
300 #pragma mark --- External Interfaces
302 - (void)rpcEpoch:(OTCuttlefishContext*)cuttlefishContext
303 reply:(void (^)(uint64_t epoch,
304 NSError * _Nullable error))reply
306 OTEpochOperation* pendingOp = [[OTEpochOperation alloc] init:self.containerName
307 contextID:self.contextID
308 intendedState:OctagonStateAcceptorAwaitingIdentity
309 errorState:OctagonStateAcceptorDone
310 cuttlefishXPCWrapper:cuttlefishContext.cuttlefishXPCWrapper];
312 OctagonStateTransitionRequest<OTEpochOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"rpcEpoch"
313 sourceStates:[NSSet setWithArray:@[OctagonStateAcceptorBeginClientJoin]]
314 serialQueue:self.queue
315 timeout:2*NSEC_PER_SEC
316 transitionOp:pendingOp];
318 CKKSResultOperation* callback = [CKKSResultOperation named:@"rpcEpoch-callback"
320 secnotice("otrpc", "Returning an epoch call: %llu %@", pendingOp.epoch, pendingOp.error);
321 reply(pendingOp.epoch,
324 [callback addDependency:pendingOp];
325 [self.operationQueue addOperation: callback];
327 [self handleExternalClientStateMachineRequest:request client:self.clientName];
332 - (void)rpcVoucher:(OTCuttlefishContext*)cuttlefishContext
333 peerID:(NSString*)peerID
334 permanentInfo:(NSData *)permanentInfo
335 permanentInfoSig:(NSData *)permanentInfoSig
336 stableInfo:(NSData *)stableInfo
337 stableInfoSig:(NSData *)stableInfoSig
338 reply:(void (^)(NSData* voucher, NSData* voucherSig, NSError * _Nullable error))reply
340 OTClientVoucherOperation* pendingOp = [[OTClientVoucherOperation alloc] initWithDependencies:cuttlefishContext.operationDependencies
341 intendedState:OctagonStateAcceptorVoucherPrepared
342 errorState:OctagonStateAcceptorDone
343 deviceInfo:[cuttlefishContext prepareInformation]
345 permanentInfo:permanentInfo
346 permanentInfoSig:permanentInfoSig
347 stableInfo:stableInfo
348 stableInfoSig:stableInfoSig];
350 OctagonStateTransitionRequest<OTClientVoucherOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"rpcVoucher"
351 sourceStates:[NSSet setWithArray:@[OctagonStateAcceptorAwaitingIdentity]]
352 serialQueue:self.queue
353 timeout:2*NSEC_PER_SEC
355 transitionOp:pendingOp];
356 CKKSResultOperation* callback = [CKKSResultOperation named:@"rpcVoucher-callback"
358 secnotice("otrpc", "Returning a voucher call: %@, %@, %@", pendingOp.voucher, pendingOp.voucherSig, pendingOp.error);
359 reply(pendingOp.voucher, pendingOp.voucherSig, pendingOp.error);
361 [callback addDependency:pendingOp];
362 [self.operationQueue addOperation: callback];
364 [self handleExternalClientStateMachineRequest:request client:self.clientName];