]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSUpdateDeviceStateOperation.m
Security-58286.31.2.tar.gz
[apple/security.git] / keychain / ckks / CKKSUpdateDeviceStateOperation.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
24 #if OCTAGON
25
26 #include <utilities/SecInternalReleasePriv.h>
27 #import "keychain/ckks/CKKSKeychainView.h"
28 #import "keychain/ckks/CKKSUpdateDeviceStateOperation.h"
29 #import "keychain/ckks/CKKSCurrentKeyPointer.h"
30 #import "keychain/ckks/CKKSKey.h"
31 #import "keychain/ckks/CKKSLockStateTracker.h"
32 #import "keychain/ckks/CKKSSQLDatabaseObject.h"
33
34 @interface CKKSUpdateDeviceStateOperation ()
35 @property CKModifyRecordsOperation* modifyRecordsOperation;
36 @property CKOperationGroup* group;
37 @property bool rateLimit;
38 @end
39
40 @implementation CKKSUpdateDeviceStateOperation
41
42 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks rateLimit:(bool)rateLimit ckoperationGroup:(CKOperationGroup*)group {
43 if((self = [super init])) {
44 _ckks = ckks;
45 _group = group;
46 _rateLimit = rateLimit;
47 }
48 return self;
49 }
50
51 - (void)groupStart {
52 CKKSKeychainView* ckks = self.ckks;
53 if(!ckks) {
54 ckkserror("ckksdevice", ckks, "no CKKS object");
55 self.error = [NSError errorWithDomain:@"securityd" code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"no CKKS object"}];
56 return;
57 }
58
59 CKKSCKAccountStateTracker* accountTracker = ckks.accountTracker;
60 if(!accountTracker) {
61 ckkserror("ckksdevice", ckks, "no AccountTracker object");
62 self.error = [NSError errorWithDomain:@"securityd" code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"no AccountTracker object"}];
63 return;
64 }
65
66 __weak __typeof(self) weakSelf = self;
67
68 // We must have the ck device ID to run this operation.
69 if([accountTracker.ckdeviceIDInitialized wait:200*NSEC_PER_SEC]) {
70 ckkserror("ckksdevice", ckks, "CK device ID not initialized, quitting");
71 self.error = [NSError errorWithDomain:@"securityd" code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"CK device ID not initialized"}];
72 return;
73 }
74
75 if(!accountTracker.ckdeviceID) {
76 ckkserror("ckksdevice", ckks, "CK device ID not initialized, quitting");
77 self.error = [NSError errorWithDomain:@"securityd"
78 code:errSecInternalError
79 userInfo:@{NSLocalizedDescriptionKey: @"CK device ID null", NSUnderlyingErrorKey:CKKSNilToNSNull(accountTracker.ckdeviceIDError)}];
80 return;
81 }
82
83 [ckks dispatchSyncWithAccountKeys:^bool {
84 NSError* error = nil;
85
86 CKKSDeviceStateEntry* cdse = [ckks _onqueueCurrentDeviceStateEntry:&error];
87 if(error) {
88 ckkserror("ckksdevice", ckks, "Error creating device state entry; quitting: %@", error);
89 return false;
90 }
91
92 if(self.rateLimit) {
93 NSDate* lastUpdate = cdse.storedCKRecord.modificationDate;
94
95 // Only upload this every 3 days (1 day for internal installs)
96 NSDate* now = [NSDate date];
97 NSDateComponents* offset = [[NSDateComponents alloc] init];
98 if(SecIsInternalRelease()) {
99 [offset setHour:-23];
100 } else {
101 [offset setHour:-3*24];
102 }
103 NSDate* deadline = [[NSCalendar currentCalendar] dateByAddingComponents:offset toDate:now options:0];
104
105 if(lastUpdate == nil || [lastUpdate compare: deadline] == NSOrderedAscending) {
106 ckksnotice("ckksdevice", ckks, "Not rate-limiting: last updated %@ vs %@", lastUpdate, deadline);
107 } else {
108 ckksnotice("ckksdevice", ckks, "Last update is within 3 days (%@); rate-limiting this operation", lastUpdate);
109 self.error = [NSError errorWithDomain:@"securityd"
110 code:errSecInternalError
111 userInfo:@{NSLocalizedDescriptionKey: @"Rate-limited the CKKSUpdateDeviceStateOperation"}];
112 return false;
113 }
114 }
115
116 ckksnotice("ckksdevice", ckks, "Saving new device state %@", cdse);
117
118 NSArray* recordsToSave = @[[cdse CKRecordWithZoneID:ckks.zoneID]];
119
120 // Start a CKModifyRecordsOperation to save this new/updated record.
121 NSBlockOperation* modifyComplete = [[NSBlockOperation alloc] init];
122 modifyComplete.name = @"updateDeviceState-modifyRecordsComplete";
123 [self dependOnBeforeGroupFinished: modifyComplete];
124
125 self.modifyRecordsOperation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:recordsToSave recordIDsToDelete:nil];
126 self.modifyRecordsOperation.atomic = TRUE;
127 self.modifyRecordsOperation.timeoutIntervalForRequest = 2;
128 self.modifyRecordsOperation.qualityOfService = NSQualityOfServiceUtility;
129 self.modifyRecordsOperation.savePolicy = CKRecordSaveAllKeys; // Overwrite anything in CloudKit: this is our state now
130 self.modifyRecordsOperation.group = self.group;
131
132 self.modifyRecordsOperation.perRecordCompletionBlock = ^(CKRecord *record, NSError * _Nullable error) {
133 __strong __typeof(weakSelf) strongSelf = weakSelf;
134 __strong __typeof(strongSelf.ckks) blockCKKS = strongSelf.ckks;
135
136 if(!error) {
137 ckksnotice("ckksdevice", blockCKKS, "Device state record upload successful for %@: %@", record.recordID.recordName, record);
138 } else {
139 ckkserror("ckksdevice", blockCKKS, "error on row: %@ %@", error, record);
140 }
141 };
142
143 self.modifyRecordsOperation.modifyRecordsCompletionBlock = ^(NSArray<CKRecord *> *savedRecords, NSArray<CKRecordID *> *deletedRecordIDs, NSError *ckerror) {
144 __strong __typeof(weakSelf) strongSelf = weakSelf;
145 __strong __typeof(strongSelf.ckks) strongCKKS = strongSelf.ckks;
146 if(!strongSelf || !strongCKKS) {
147 ckkserror("ckksdevice", strongCKKS, "received callback for released object");
148 strongSelf.error = [NSError errorWithDomain:@"securityd" code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"no CKKS object"}];
149 [strongSelf runBeforeGroupFinished:modifyComplete];
150 return;
151 }
152
153 if(ckerror) {
154 ckkserror("ckksdevice", strongCKKS, "CloudKit returned an error: %@", ckerror);
155 strongSelf.error = ckerror;
156 [strongSelf runBeforeGroupFinished:modifyComplete];
157 return;
158 }
159
160 __block NSError* error = nil;
161
162 [strongCKKS dispatchSync: ^bool{
163 for(CKRecord* record in savedRecords) {
164 // Save the item records
165 if([record.recordType isEqualToString: SecCKRecordDeviceStateType]) {
166 CKKSDeviceStateEntry* newcdse = [[CKKSDeviceStateEntry alloc] initWithCKRecord:record];
167 [newcdse saveToDatabase:&error];
168 if(error) {
169 ckkserror("ckksdevice", strongCKKS, "Couldn't save new device state(%@) to database: %@", newcdse, error);
170 }
171 }
172 }
173 return true;
174 }];
175
176 strongSelf.error = error;
177 [strongSelf runBeforeGroupFinished:modifyComplete];
178 };
179
180 [self dependOnBeforeGroupFinished: self.modifyRecordsOperation];
181 [ckks.database addOperation: self.modifyRecordsOperation];
182
183 return true;
184 }];
185 }
186
187 @end
188
189 #endif // OCTAGON