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