2 * Copyright (c) 2019 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@
26 #import "keychain/ot/OTCheckHealthOperation.h"
27 #import "keychain/ot/OTOperationDependencies.h"
28 #import "keychain/ot/OTStates.h"
29 #import "keychain/ot/ObjCImprovements.h"
30 #import "keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h"
31 #import <Security/SecInternalReleasePriv.h>
33 #if !TARGET_OS_SIMULATOR
34 #import <MobileKeyBag/MobileKeyBag.h>
37 #if TARGET_OS_MAC && !TARGET_OS_SIMULATOR
41 @interface OTCheckHealthOperation ()
42 @property OTOperationDependencies* deps;
44 @property NSOperation* finishOp;
45 @property BOOL requiresEscrowCheck;
48 @implementation OTCheckHealthOperation
49 @synthesize intendedState = _intendedState;
51 - (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies
52 intendedState:(OctagonState*)intendedState
53 errorState:(OctagonState*)errorState
54 deviceInfo:(nonnull OTDeviceInformation *)deviceInfo
55 skipRateLimitedCheck:(BOOL)skipRateLimitedCheck
57 if((self = [super init])) {
59 _intendedState = intendedState;
60 _nextState = errorState;
64 _skipRateLimitingCheck = skipRateLimitedCheck;
69 - (BOOL) checkIfPasscodeIsSetForDevice
71 BOOL passcodeIsSet = NO;
72 #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
73 int lockState = MKBGetDeviceLockState(NULL);
74 if (lockState != kMobileKeyBagDisabled && lockState >= 0) {
77 #elif TARGET_OS_MAC && !TARGET_OS_SIMULATOR
78 NSDictionary *options = @{ (id)kKeyBagDeviceHandle : [[NSNumber alloc] initWithInt: getuid()] };
79 int lockState = MKBGetDeviceLockState((__bridge CFDictionaryRef)(options));
80 if (lockState != kMobileKeyBagDisabled && lockState >= 0) {
90 secnotice("octagon-health", "Beginning cuttlefish health checkup");
92 self.finishOp = [[NSOperation alloc] init];
93 [self dependOnBeforeGroupFinished:self.finishOp];
95 if(self.skipRateLimitingCheck == NO) {
96 secnotice("octagon-health", "running rate limiting checks!");
97 NSDate* lastUpdate = nil;
98 NSError* accountLoadError = nil;
101 lastUpdate = [self.deps.stateHolder lastHealthCheckupDate:&accountLoadError];
102 if([self.deps.viewManager.lockStateTracker isLockedError: accountLoadError]) {
103 secnotice("octagon-health", "device is locked, not performing cuttlefish check");
104 [self runBeforeGroupFinished:self.finishOp];
107 secnotice("octagon-health", "last health check timestamp: %@", lastUpdate);
109 // Only query cuttlefish for trust status every 3 days (1 day for internal installs)
110 NSDateComponents* offset = [[NSDateComponents alloc] init];
111 if(SecIsInternalRelease()) {
112 [offset setHour:-23];
114 [offset setHour:-3*24];
116 NSDate *now = [NSDate date];
117 NSDate* deadline = [[NSCalendar currentCalendar] dateByAddingComponents:offset toDate:now options:0];
119 if(lastUpdate == nil || [lastUpdate compare: deadline] == NSOrderedAscending) {
120 secnotice("octagon-health", "Not rate-limiting: last updated %@ vs %@", lastUpdate, deadline);
122 secnotice("octagon-health", "Last update is within 3 days (%@); rate-limiting this operation", lastUpdate);
123 NSString *description = [NSString stringWithFormat:@"Rate-limited the OTCheckHealthOperation:%@", lastUpdate];
124 NSError *rateLimitedError = [NSError errorWithDomain:@"securityd"
125 code:errSecInternalError
126 userInfo:@{NSLocalizedDescriptionKey: description}];
127 secnotice("octagon-health", "rate limited! %@", rateLimitedError);
128 self.nextState = self.intendedState; //not setting the error on the results op as I don't want a CFU posted.
129 [self runBeforeGroupFinished:self.finishOp];
132 NSError* persistedError = nil;
133 BOOL persisted = [self.deps.stateHolder persistLastHealthCheck:now error:&persistedError];
135 if([self.deps.viewManager.lockStateTracker isLockedError: persistedError]) {
136 secnotice("octagon-health", "device is locked, not performing cuttlefish check");
137 [self runBeforeGroupFinished:self.finishOp];
140 if(persisted == NO || persistedError) {
141 secerror("octagon-health: failed to persist last health check value:%@", persistedError);
142 [self runBeforeGroupFinished:self.finishOp];
146 secnotice("octagon-health", "NOT running rate limiting checks!");
150 [self.deps.cuttlefishXPCWrapper requestHealthCheckWithContainer:self.deps.containerName
151 context:self.deps.contextID
152 requiresEscrowCheck: [self checkIfPasscodeIsSetForDevice]
153 reply:^(BOOL postRepairCFU, BOOL postEscrowCFU, BOOL resetOctagon, NSError *error) {
156 secerror("octagon-health: error: %@", error);
159 [self runBeforeGroupFinished:self.finishOp];
162 secnotice("octagon-health", "cuttlefish came back with these suggestions\n: post repair? %d\n, post escrow? %d\n, reset octagon? %d", postRepairCFU, postEscrowCFU, resetOctagon);
163 [self handleRepairSuggestions:postRepairCFU
164 postEscrowCFU:postEscrowCFU
165 resetOctagon:resetOctagon];
170 - (void)handleRepairSuggestions:(BOOL)postRepairCFU postEscrowCFU:(BOOL)postEscrowCFU resetOctagon:(BOOL)resetOctagon
172 self.postEscrowCFU = postEscrowCFU;
173 self.postRepairCFU = postRepairCFU;
174 self.resetOctagon = resetOctagon;
177 secnotice("octagon-health", "Resetting Octagon as per Cuttlefish request");
178 self.nextState = OctagonStateHealthCheckReset;
180 self.nextState = self.intendedState;
183 [self runBeforeGroupFinished:self.finishOp];