]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSUpdateDeviceStateOperation.m
Security-59754.41.1.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 #import "keychain/ot/ObjCImprovements.h"
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
60 CKKSAccountStateTracker* accountTracker = ckks.accountTracker;
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
67 WEAKIFY(self);
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
76 NSString* ckdeviceID = accountTracker.ckdeviceID;
77 if(!ckdeviceID) {
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
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
90 [ckks dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult{
91 NSError* error = nil;
92
93 CKKSDeviceStateEntry* cdse = [ckks _onqueueCurrentDeviceStateEntry:&error];
94 if(error || !cdse) {
95 ckkserror("ckksdevice", ckks, "Error creating device state entry; quitting: %@", error);
96 return CKKSDatabaseTransactionRollback;
97 }
98
99 if(self.rateLimit) {
100 NSDate* lastUpdate = cdse.storedCKRecord.modificationDate;
101
102 // Only upload this every 3 days (1 day for internal installs)
103 NSDate* now = [NSDate date];
104 NSDateComponents* offset = [[NSDateComponents alloc] init];
105 if(SecIsInternalRelease()) {
106 [offset setHour:-23];
107 } else {
108 [offset setHour:-3*24];
109 }
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 CKKSDatabaseTransactionRollback;
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;
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) {
139 STRONGIFY(self);
140 CKKSKeychainView* blockCKKS = self.ckks;
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) {
150 STRONGIFY(self);
151 CKKSKeychainView* strongCKKS = self.ckks;
152 if(!self || !strongCKKS) {
153 ckkserror("ckksdevice", strongCKKS, "received callback for released object");
154 self.error = [NSError errorWithDomain:@"securityd" code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"no CKKS object"}];
155 [self runBeforeGroupFinished:modifyComplete];
156 return;
157 }
158
159 if(ckerror) {
160 ckkserror("ckksdevice", strongCKKS, "CloudKit returned an error: %@", ckerror);
161 self.error = ckerror;
162 [self runBeforeGroupFinished:modifyComplete];
163 return;
164 }
165
166 __block NSError* error = nil;
167
168 [strongCKKS dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult{
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 CKKSDatabaseTransactionCommit;
180 }];
181
182 self.error = error;
183 [self runBeforeGroupFinished:modifyComplete];
184 };
185
186 [self dependOnBeforeGroupFinished: self.modifyRecordsOperation];
187 [ckks.database addOperation: self.modifyRecordsOperation];
188
189 return CKKSDatabaseTransactionCommit;
190 }];
191 }
192
193 @end
194
195 #endif // OCTAGON