]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSDeviceStateEntry.m
Security-58286.60.28.tar.gz
[apple/security.git] / keychain / ckks / CKKSDeviceStateEntry.m
1 /*
2 * Copyright (c) 2016 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 <AssertMacros.h>
27
28 #import <Foundation/Foundation.h>
29
30 #import <CloudKit/CloudKit.h>
31 #import <CloudKit/CloudKit_Private.h>
32 #import "keychain/ckks/CKKSDeviceStateEntry.h"
33 #import "keychain/ckks/CKKSKeychainView.h"
34 #include <Security/SecureObjectSync/SOSAccount.h>
35
36 @implementation CKKSDeviceStateEntry
37
38 - (instancetype)initForDevice:(NSString*)device
39 osVersion:(NSString*)osVersion
40 lastUnlockTime:(NSDate*)lastUnlockTime
41 circlePeerID:(NSString*)circlePeerID
42 circleStatus:(SOSCCStatus)circleStatus
43 keyState:(CKKSZoneKeyState*)keyState
44 currentTLKUUID:(NSString*)currentTLKUUID
45 currentClassAUUID:(NSString*)currentClassAUUID
46 currentClassCUUID:(NSString*)currentClassCUUID
47 zoneID:(CKRecordZoneID*)zoneID
48 encodedCKRecord:(NSData*)encodedrecord
49 {
50 if((self = [super initWithCKRecordType:SecCKRecordDeviceStateType
51 encodedCKRecord:encodedrecord
52 zoneID:zoneID])) {
53 _device = device;
54 _osVersion = osVersion;
55 _lastUnlockTime = lastUnlockTime;
56
57 _circleStatus = circleStatus;
58 _keyState = keyState;
59
60 _circlePeerID = circlePeerID;
61
62 _currentTLKUUID = currentTLKUUID;
63 _currentClassAUUID = currentClassAUUID;
64 _currentClassCUUID = currentClassCUUID;
65 }
66 return self;
67 }
68
69 #define kSOSCCErrorPositive 111
70
71 -(id)sosCCStatusToCKType:(SOSCCStatus)status {
72 // kSOSCCError is -1, but without a size.
73 // make it a special number
74 if(status == kSOSCCError) {
75 [NSNumber numberWithInt:kSOSCCErrorPositive];
76 }
77 return [NSNumber numberWithInt:status];
78 }
79
80 -(SOSCCStatus)cktypeToSOSCCStatus:(id)object {
81 if(![object isKindOfClass:[NSNumber class]]) {
82 return kSOSCCError;
83 }
84 NSNumber* number = (NSNumber*)object;
85
86 uint32_t n = [number unsignedIntValue];
87
88 switch(n) {
89 case (uint32_t)kSOSCCInCircle:
90 return kSOSCCInCircle;
91 case (uint32_t)kSOSCCNotInCircle:
92 return kSOSCCNotInCircle;
93 case (uint32_t)kSOSCCRequestPending:
94 return kSOSCCRequestPending;
95 case (uint32_t)kSOSCCCircleAbsent:
96 return kSOSCCCircleAbsent;
97 case (uint32_t)kSOSCCErrorPositive: // Use the magic number
98 return kSOSCCError;
99 case (uint32_t)kSOSCCError: // And, if by some miracle, you end up with -1 as a uint32_t, accept that too
100 return kSOSCCError;
101 default:
102 secerror("ckks: %d is not an SOSCCStatus?", n);
103 return kSOSCCError;
104 }
105 }
106
107 +(NSString*)nameFromCKRecordID:(CKRecordID*)recordID {
108 // Strip off the prefix from the recordName
109 NSString* prefix = @"ckid-";
110 NSString* name = recordID.recordName;
111
112 if ([name hasPrefix:prefix]) {
113 name = [name substringFromIndex:[prefix length]];
114 }
115 return name;
116 }
117
118 -(NSString*)description {
119 NSDate* updated = self.storedCKRecord.modificationDate;
120
121 return [NSString stringWithFormat:@"<CKKSDeviceStateEntry(%@,%@,%@,%@,%@): %@ %@ %@ %@ %@ upd:%@>",
122 self.device,
123 self.circlePeerID,
124 self.osVersion,
125 self.lastUnlockTime,
126 self.zoneID.zoneName,
127 SOSAccountGetSOSCCStatusString(self.circleStatus),
128 self.keyState,
129 self.currentTLKUUID,
130 self.currentClassAUUID,
131 self.currentClassCUUID,
132 updated ? updated : @"unknown"
133 ];
134 }
135
136 - (BOOL)isEqual: (id) object {
137 if(![object isKindOfClass:[CKKSDeviceStateEntry class]]) {
138 return NO;
139 }
140
141 CKKSDeviceStateEntry* obj = (CKKSDeviceStateEntry*) object;
142
143 return ([self.zoneID isEqual: obj.zoneID] &&
144 ((self.device == nil && obj.device == nil) || [self.device isEqual: obj.device]) &&
145 ((self.osVersion == nil && obj.osVersion == nil) || [self.osVersion isEqual:obj.osVersion]) &&
146 ((self.lastUnlockTime == nil && obj.lastUnlockTime == nil) || [self.lastUnlockTime isEqual:obj.lastUnlockTime]) &&
147 ((self.circlePeerID == nil && obj.circlePeerID == nil) || [self.circlePeerID isEqual: obj.circlePeerID]) &&
148 (self.circleStatus == obj.circleStatus) &&
149 ((self.keyState == nil && obj.keyState == nil) || [self.keyState isEqual: obj.keyState]) &&
150 ((self.currentTLKUUID == nil && obj.currentTLKUUID == nil) || [self.currentTLKUUID isEqual: obj.currentTLKUUID]) &&
151 ((self.currentClassAUUID == nil && obj.currentClassAUUID == nil) || [self.currentClassAUUID isEqual: obj.currentClassAUUID]) &&
152 ((self.currentClassCUUID == nil && obj.currentClassCUUID == nil) || [self.currentClassCUUID isEqual: obj.currentClassCUUID]) &&
153 YES) ? YES : NO;
154 }
155
156 #pragma mark - Database Operations
157
158 + (instancetype)fromDatabase:(NSString*)device zoneID:(CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *)error {
159 return [self fromDatabaseWhere: @{@"device":CKKSNilToNSNull(device), @"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
160 }
161
162 + (instancetype)tryFromDatabase:(NSString*)device zoneID:(CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *)error {
163 return [self tryFromDatabaseWhere: @{@"device":CKKSNilToNSNull(device), @"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
164 }
165
166 + (instancetype)tryFromDatabaseFromCKRecordID:(CKRecordID*)recordID error:(NSError * __autoreleasing *)error {
167 return [self tryFromDatabaseWhere: @{@"device":CKKSNilToNSNull([self nameFromCKRecordID:recordID]), @"ckzone": CKKSNilToNSNull(recordID.zoneID.zoneName)} error:error];
168 }
169
170 + (NSArray<CKKSDeviceStateEntry*>*)allInZone:(CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *)error {
171 return [self allWhere:@{@"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
172 }
173
174 #pragma mark - CKKSCKRecordHolder methods
175
176 - (NSString*)CKRecordName {
177 return [NSString stringWithFormat:@"ckid-%@", self.device];
178 }
179
180 - (CKRecord*)updateCKRecord: (CKRecord*) record zoneID: (CKRecordZoneID*) zoneID {
181 if(![record.recordID.recordName isEqualToString: [self CKRecordName]]) {
182 @throw [NSException
183 exceptionWithName:@"WrongCKRecordNameException"
184 reason:[NSString stringWithFormat: @"CKRecord name (%@) was not %@", record.recordID.recordName, [self CKRecordName]]
185 userInfo:nil];
186 }
187 if(![record.recordType isEqualToString: SecCKRecordDeviceStateType]) {
188 @throw [NSException
189 exceptionWithName:@"WrongCKRecordTypeException"
190 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordDeviceStateType]
191 userInfo:nil];
192 }
193
194 record[SecCKSRecordOSVersionKey] = self.osVersion;
195 record[SecCKSRecordLastUnlockTime] = self.lastUnlockTime;
196
197 record[SecCKRecordCircleStatus] = [self sosCCStatusToCKType: self.circleStatus];
198 record[SecCKRecordKeyState] = CKKSZoneKeyToNumber(self.keyState);
199
200 record[SecCKRecordCirclePeerID] = self.circlePeerID;
201
202 #define CKKeyRef(uuid) (!uuid ? nil : [[CKReference alloc] initWithRecordID:[[CKRecordID alloc] initWithRecordName:uuid \
203 zoneID:self.zoneID] \
204 action: CKReferenceActionNone])
205
206 record[SecCKRecordCurrentTLK] = CKKeyRef(self.currentTLKUUID);
207 record[SecCKRecordCurrentClassA] = CKKeyRef(self.currentClassAUUID);
208 record[SecCKRecordCurrentClassC] = CKKeyRef(self.currentClassCUUID);
209
210 return record;
211 }
212
213 - (bool)matchesCKRecord: (CKRecord*) record {
214 if(![record.recordType isEqualToString: SecCKRecordDeviceStateType]) {
215 return false;
216 }
217
218 if(![record.recordID.recordName isEqualToString: [self CKRecordName]]) {
219 return false;
220 }
221
222 if((!(self.lastUnlockTime == nil && record[SecCKSRecordLastUnlockTime] == nil)) &&
223 ![record[SecCKSRecordLastUnlockTime] isEqual: self.lastUnlockTime]) {
224 return false;
225 }
226
227 if((!(self.osVersion == nil && record[SecCKSRecordOSVersionKey] == nil)) &&
228 ![record[SecCKSRecordOSVersionKey] isEqualToString: self.osVersion]) {
229 return false;
230 }
231
232 if((!(self.circlePeerID == nil && record[SecCKRecordCirclePeerID] == nil)) &&
233 ![record[SecCKRecordCirclePeerID] isEqualToString: self.circlePeerID]) {
234 return false;
235 }
236
237 if([self cktypeToSOSCCStatus: record[SecCKRecordCircleStatus]] != self.circleStatus) {
238 return false;
239 }
240
241 if(![CKKSZoneKeyRecover(record[SecCKRecordKeyState]) isEqualToString: self.keyState]) {
242 return false;
243 }
244
245 if(![[[record[SecCKRecordCurrentTLK] recordID] recordName] isEqualToString: self.currentTLKUUID]) {
246 return false;
247 }
248 if(![[[record[SecCKRecordCurrentClassA] recordID] recordName] isEqualToString: self.currentTLKUUID]) {
249 return false;
250 }
251 if(![[[record[SecCKRecordCurrentClassC] recordID] recordName] isEqualToString: self.currentTLKUUID]) {
252 return false;
253 }
254
255 return true;
256 }
257
258 - (void)setFromCKRecord: (CKRecord*) record {
259 if(![record.recordType isEqualToString: SecCKRecordDeviceStateType]) {
260 @throw [NSException
261 exceptionWithName:@"WrongCKRecordTypeException"
262 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordDeviceStateType]
263 userInfo:nil];
264 }
265
266 [self setStoredCKRecord:record];
267
268 self.osVersion = record[SecCKSRecordOSVersionKey];
269 self.lastUnlockTime = record[SecCKSRecordLastUnlockTime];
270 self.device = [CKKSDeviceStateEntry nameFromCKRecordID: record.recordID];
271
272 self.circlePeerID = record[SecCKRecordCirclePeerID];
273
274 self.circleStatus = [self cktypeToSOSCCStatus:record[SecCKRecordCircleStatus]];
275 self.keyState = CKKSZoneKeyRecover(record[SecCKRecordKeyState]);
276
277 self.currentTLKUUID = [[record[SecCKRecordCurrentTLK] recordID] recordName];
278 self.currentClassAUUID = [[record[SecCKRecordCurrentClassA] recordID] recordName];
279 self.currentClassCUUID = [[record[SecCKRecordCurrentClassC] recordID] recordName];
280 }
281
282 #pragma mark - CKKSSQLDatabaseObject methods
283
284 + (NSString*)sqlTable {
285 return @"ckdevicestate";
286 }
287
288 + (NSArray<NSString*>*)sqlColumns {
289 return @[@"device", @"ckzone", @"osversion", @"lastunlock", @"peerid", @"circlestatus", @"keystate", @"currentTLK", @"currentClassA", @"currentClassC", @"ckrecord"];
290 }
291
292 - (NSDictionary<NSString*,NSString*>*)whereClauseToFindSelf {
293 return @{@"device":self.device, @"ckzone":self.zoneID.zoneName};
294 }
295
296 - (NSDictionary<NSString*,NSString*>*)sqlValues {
297 NSISO8601DateFormatter* dateFormat = [[NSISO8601DateFormatter alloc] init];
298
299 return @{@"device": self.device,
300 @"ckzone": CKKSNilToNSNull(self.zoneID.zoneName),
301 @"osversion": CKKSNilToNSNull(self.osVersion),
302 @"lastunlock": CKKSNilToNSNull(self.lastUnlockTime ? [dateFormat stringFromDate:self.lastUnlockTime] : nil),
303 @"peerid": CKKSNilToNSNull(self.circlePeerID),
304 @"circlestatus": (__bridge NSString*)SOSAccountGetSOSCCStatusString(self.circleStatus),
305 @"keystate": CKKSNilToNSNull(self.keyState),
306 @"currentTLK": CKKSNilToNSNull(self.currentTLKUUID),
307 @"currentClassA": CKKSNilToNSNull(self.currentClassAUUID),
308 @"currentClassC": CKKSNilToNSNull(self.currentClassCUUID),
309 @"ckrecord": CKKSNilToNSNull([self.encodedCKRecord base64EncodedStringWithOptions:0]),
310 };
311 }
312
313 + (instancetype)fromDatabaseRow:(NSDictionary*)row {
314 NSISO8601DateFormatter* dateFormat = [[NSISO8601DateFormatter alloc] init];
315
316 return [[CKKSDeviceStateEntry alloc] initForDevice:row[@"device"]
317 osVersion:CKKSNSNullToNil(row[@"osversion"])
318 lastUnlockTime:[row[@"lastunlock"] isEqual: [NSNull null]] ? nil : [dateFormat dateFromString: row[@"lastunlock"]]
319 circlePeerID:CKKSNSNullToNil(row[@"peerid"])
320 circleStatus:SOSAccountGetSOSCCStatusFromString((__bridge CFStringRef) CKKSNSNullToNil(row[@"circlestatus"]))
321 keyState:CKKSNSNullToNil(row[@"keystate"])
322 currentTLKUUID:CKKSNSNullToNil(row[@"currentTLK"])
323 currentClassAUUID:CKKSNSNullToNil(row[@"currentClassA"])
324 currentClassCUUID:CKKSNSNullToNil(row[@"currentClassC"])
325 zoneID:[[CKRecordZoneID alloc] initWithZoneName: row[@"ckzone"] ownerName:CKCurrentUserDefaultName]
326 encodedCKRecord:CKKSUnbase64NullableString(row[@"ckrecord"])
327 ];
328 }
329
330 @end
331
332 #endif
333