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/ObjCImprovements.h"
29 #import "keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h"
30 #import <Security/SecInternalReleasePriv.h>
32 #if !TARGET_OS_SIMULATOR
33 #import <MobileKeyBag/MobileKeyBag.h>
36 #if TARGET_OS_MAC && !TARGET_OS_SIMULATOR
40 @interface OTCheckHealthOperation ()
41 @property OTOperationDependencies* deps;
43 @property NSOperation* finishOp;
44 @property BOOL requiresEscrowCheck;
47 @implementation OTCheckHealthOperation
48 @synthesize intendedState = _intendedState;
50 - (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies
51 intendedState:(OctagonState*)intendedState
52 errorState:(OctagonState*)errorState
53 deviceInfo:(nonnull OTDeviceInformation *)deviceInfo
54 skipRateLimitedCheck:(BOOL)skipRateLimitedCheck
56 if((self = [super init])) {
58 _intendedState = intendedState;
59 _nextState = errorState;
63 _skipRateLimitingCheck = skipRateLimitedCheck;
68 - (BOOL) checkIfPasscodeIsSetForDevice
70 BOOL passcodeIsSet = NO;
71 #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
72 int lockState = MKBGetDeviceLockState(NULL);
73 if (lockState != kMobileKeyBagDisabled && lockState >= 0) {
76 #elif TARGET_OS_MAC && !TARGET_OS_SIMULATOR
77 NSDictionary *options = @{ (id)kKeyBagDeviceHandle : [[NSNumber alloc] initWithInt: getuid()] };
78 int lockState = MKBGetDeviceLockState((__bridge CFDictionaryRef)(options));
79 if (lockState != kMobileKeyBagDisabled && lockState >= 0) {
89 secnotice("octagon-health", "Beginning cuttlefish health checkup");
91 self.finishOp = [[NSOperation alloc] init];
92 [self dependOnBeforeGroupFinished:self.finishOp];
94 if(self.skipRateLimitingCheck == NO) {
95 secnotice("octagon-health", "running rate limiting checks!");
96 NSDate* lastUpdate = nil;
97 NSError* accountLoadError = nil;
100 lastUpdate = [self.deps.stateHolder lastHealthCheckupDate:&accountLoadError];
101 if([self.deps.viewManager.lockStateTracker isLockedError: accountLoadError]) {
102 secnotice("octagon-health", "device is locked, not performing cuttlefish check");
103 [self runBeforeGroupFinished:self.finishOp];
106 secnotice("octagon-health", "last health check timestamp: %@", lastUpdate);
108 // Only query cuttlefish for trust status every 3 days (1 day for internal installs)
109 NSDateComponents* offset = [[NSDateComponents alloc] init];
110 if(SecIsInternalRelease()) {
111 [offset setHour:-23];
113 [offset setHour:-3*24];
115 NSDate *now = [NSDate date];
116 NSDate* deadline = [[NSCalendar currentCalendar] dateByAddingComponents:offset toDate:now options:0];
118 if(lastUpdate == nil || [lastUpdate compare: deadline] == NSOrderedAscending) {
119 secnotice("octagon-health", "Not rate-limiting: last updated %@ vs %@", lastUpdate, deadline);
121 secnotice("octagon-health", "Last update is within 3 days (%@); rate-limiting this operation", lastUpdate);
122 NSString *description = [NSString stringWithFormat:@"Rate-limited the OTCheckHealthOperation:%@", lastUpdate];
123 NSError *rateLimitedError = [NSError errorWithDomain:@"securityd"
124 code:errSecInternalError
125 userInfo:@{NSLocalizedDescriptionKey: description}];
126 secnotice("octagon-health", "rate limited! %@", rateLimitedError);
127 self.nextState = self.intendedState; //not setting the error on the results op as I don't want a CFU posted.
128 [self runBeforeGroupFinished:self.finishOp];
131 NSError* persistedError = nil;
132 BOOL persisted = [self.deps.stateHolder persistLastHealthCheck:now error:&persistedError];
134 if([self.deps.viewManager.lockStateTracker isLockedError: persistedError]) {
135 secnotice("octagon-health", "device is locked, not performing cuttlefish check");
136 [self runBeforeGroupFinished:self.finishOp];
139 if(persisted == NO || persistedError) {
140 secerror("octagon-health: failed to persist last health check value:%@", persistedError);
141 [self runBeforeGroupFinished:self.finishOp];
145 secnotice("octagon-health", "NOT running rate limiting checks!");
149 [[self.deps.cuttlefishXPC remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
151 secerror("octagon-health: Can't talk with TrustedPeersHelper: %@", error);
153 [self runBeforeGroupFinished:self.finishOp];
156 }] requestHealthCheckWithContainer:self.deps.containerName
157 context:self.deps.contextID
158 requiresEscrowCheck: [self checkIfPasscodeIsSetForDevice]
159 reply:^(BOOL postRepairCFU, BOOL postEscrowCFU, BOOL resetOctagon, NSError *error) {
162 secerror("octagon-health: error: %@", error);
165 secnotice("octagon-health", "cuttlefish came back with these suggestions\n: post repair? %d\n, post escrow? %d\n, reset octagon? %d", postRepairCFU, postEscrowCFU, resetOctagon);
166 self.postEscrowCFU = postEscrowCFU;
167 self.postRepairCFU = postRepairCFU;
168 self.resetOctagon = resetOctagon;
170 self.nextState = self.intendedState;
172 [self runBeforeGroupFinished:self.finishOp];