]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSDeviceStateEntry.m
Security-59754.80.3.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 "keychain/SecureObjectSync/SOSAccount.h"
35
36 @implementation CKKSDeviceStateEntry
37
38 - (instancetype)initForDevice:(NSString*)device
39 osVersion:(NSString*)osVersion
40 lastUnlockTime:(NSDate*)lastUnlockTime
41 octagonPeerID:(NSString*)octagonPeerID
42 octagonStatus:(OTCliqueStatusWrapper*)octagonStatus
43 circlePeerID:(NSString*)circlePeerID
44 circleStatus:(SOSCCStatus)circleStatus
45 keyState:(CKKSZoneKeyState*)keyState
46 currentTLKUUID:(NSString*)currentTLKUUID
47 currentClassAUUID:(NSString*)currentClassAUUID
48 currentClassCUUID:(NSString*)currentClassCUUID
49 zoneID:(CKRecordZoneID*)zoneID
50 encodedCKRecord:(NSData*)encodedrecord
51 {
52 if((self = [super initWithCKRecordType:SecCKRecordDeviceStateType
53 encodedCKRecord:encodedrecord
54 zoneID:zoneID])) {
55 _device = device;
56 _osVersion = osVersion;
57 _lastUnlockTime = lastUnlockTime;
58
59 _octagonPeerID = octagonPeerID;
60 _octagonStatus = octagonStatus;
61
62 _circleStatus = circleStatus;
63 _keyState = keyState;
64
65 _circlePeerID = circlePeerID;
66
67 _currentTLKUUID = currentTLKUUID;
68 _currentClassAUUID = currentClassAUUID;
69 _currentClassCUUID = currentClassCUUID;
70 }
71 return self;
72 }
73
74 #define kSOSCCErrorPositive 111
75
76 -(id)sosCCStatusToCKType:(SOSCCStatus)status {
77 // kSOSCCError is -1, but without a size.
78 // make it a special number
79 if(status == kSOSCCError) {
80 return [NSNumber numberWithInt:kSOSCCErrorPositive];
81 }
82 return [NSNumber numberWithInt:status];
83 }
84
85 -(SOSCCStatus)cktypeToSOSCCStatus:(id)object {
86 if(![object isKindOfClass:[NSNumber class]]) {
87 return kSOSCCError;
88 }
89 NSNumber* number = (NSNumber*)object;
90
91 uint32_t n = [number unsignedIntValue];
92
93 switch(n) {
94 case (uint32_t)kSOSCCInCircle:
95 return kSOSCCInCircle;
96 case (uint32_t)kSOSCCNotInCircle:
97 return kSOSCCNotInCircle;
98 case (uint32_t)kSOSCCRequestPending:
99 return kSOSCCRequestPending;
100 case (uint32_t)kSOSCCCircleAbsent:
101 return kSOSCCCircleAbsent;
102 case (uint32_t)kSOSCCErrorPositive: // Use the magic number
103 return kSOSCCError;
104 case (uint32_t)kSOSCCError: // And, if by some miracle, you end up with -1 as a uint32_t, accept that too
105 return kSOSCCError;
106 default:
107 ckkserror_global("ckks", "%d is not an SOSCCStatus?", n);
108 return kSOSCCError;
109 }
110 }
111
112 - (id)cliqueStatusToCKType:(OTCliqueStatusWrapper* _Nullable)status {
113 if(!status) {
114 return nil;
115 }
116 // kSOSCCError is -1, but without a size.
117 // make it a special number
118 if(status.status == CliqueStatusError) {
119 return [NSNumber numberWithInt:kSOSCCErrorPositive];
120 }
121 return [NSNumber numberWithInt:(int)status.status];
122 }
123
124 - (OTCliqueStatusWrapper* _Nullable)cktypeToOTCliqueStatusWrapper:(id)object {
125 if(object == nil) {
126 return nil;
127 }
128 if(![object isKindOfClass:[NSNumber class]]) {
129 return nil;
130 }
131 NSNumber* number = (NSNumber*)object;
132
133 uint32_t n = [number unsignedIntValue];
134
135 switch(n) {
136 case (uint32_t)CliqueStatusIn:
137 return [[OTCliqueStatusWrapper alloc] initWithStatus:CliqueStatusIn];
138 case (uint32_t)CliqueStatusNotIn:
139 return [[OTCliqueStatusWrapper alloc] initWithStatus:CliqueStatusNotIn];
140 case (uint32_t)CliqueStatusPending:
141 return [[OTCliqueStatusWrapper alloc] initWithStatus:CliqueStatusPending];
142 case (uint32_t)CliqueStatusAbsent:
143 return [[OTCliqueStatusWrapper alloc] initWithStatus:CliqueStatusAbsent];
144 case (uint32_t)CliqueStatusNoCloudKitAccount:
145 return [[OTCliqueStatusWrapper alloc] initWithStatus:CliqueStatusNoCloudKitAccount];
146 case (uint32_t)kSOSCCErrorPositive: // Use the magic number
147 return [[OTCliqueStatusWrapper alloc] initWithStatus:CliqueStatusError];
148 default:
149 ckkserror_global("ckks", "%d is not an OTCliqueStatus?", n);
150 return [[OTCliqueStatusWrapper alloc] initWithStatus:CliqueStatusError];;
151 }
152 }
153
154 +(NSString*)nameFromCKRecordID:(CKRecordID*)recordID {
155 // Strip off the prefix from the recordName
156 NSString* prefix = @"ckid-";
157 NSString* name = recordID.recordName;
158
159 if ([name hasPrefix:prefix]) {
160 name = [name substringFromIndex:[prefix length]];
161 }
162 return name;
163 }
164
165 -(NSString*)description {
166 NSDate* updated = self.storedCKRecord.modificationDate;
167
168 return [NSString stringWithFormat:@"<CKKSDeviceStateEntry(%@,%@,%@,%@,%@,%@): %@ %@ %@ %@ %@ %@ upd:%@>",
169 self.device,
170 self.circlePeerID,
171 self.octagonPeerID,
172 self.osVersion,
173 self.lastUnlockTime,
174 self.zoneID.zoneName,
175 SOSAccountGetSOSCCStatusString(self.circleStatus),
176 self.octagonStatus ? OTCliqueStatusToString(self.octagonStatus.status) : @"CliqueMissing",
177 self.keyState,
178 self.currentTLKUUID,
179 self.currentClassAUUID,
180 self.currentClassCUUID,
181 updated ? updated : @"unknown"
182 ];
183 }
184
185 - (BOOL)isEqual: (id) object {
186 if(![object isKindOfClass:[CKKSDeviceStateEntry class]]) {
187 return NO;
188 }
189
190 CKKSDeviceStateEntry* obj = (CKKSDeviceStateEntry*) object;
191
192 return ([self.zoneID isEqual: obj.zoneID] &&
193 ((self.device == nil && obj.device == nil) || [self.device isEqual: obj.device]) &&
194 ((self.osVersion == nil && obj.osVersion == nil) || [self.osVersion isEqual:obj.osVersion]) &&
195 ((self.lastUnlockTime == nil && obj.lastUnlockTime == nil) || [self.lastUnlockTime isEqual:obj.lastUnlockTime]) &&
196 ((self.octagonPeerID == nil && obj.octagonPeerID == nil) || [self.octagonPeerID isEqual: obj.octagonPeerID]) &&
197 ((self.octagonStatus == nil && obj.octagonStatus == nil) || [self.octagonStatus isEqual: obj.octagonStatus]) &&
198 ((self.circlePeerID == nil && obj.circlePeerID == nil) || [self.circlePeerID isEqual: obj.circlePeerID]) &&
199 (self.circleStatus == obj.circleStatus) &&
200 ((self.keyState == nil && obj.keyState == nil) || [self.keyState isEqual: obj.keyState]) &&
201 ((self.currentTLKUUID == nil && obj.currentTLKUUID == nil) || [self.currentTLKUUID isEqual: obj.currentTLKUUID]) &&
202 ((self.currentClassAUUID == nil && obj.currentClassAUUID == nil) || [self.currentClassAUUID isEqual: obj.currentClassAUUID]) &&
203 ((self.currentClassCUUID == nil && obj.currentClassCUUID == nil) || [self.currentClassCUUID isEqual: obj.currentClassCUUID]) &&
204 YES) ? YES : NO;
205 }
206
207 #pragma mark - Database Operations
208
209 + (instancetype)fromDatabase:(NSString*)device zoneID:(CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *)error {
210 return [self fromDatabaseWhere: @{@"device":CKKSNilToNSNull(device), @"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
211 }
212
213 + (instancetype)tryFromDatabase:(NSString*)device zoneID:(CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *)error {
214 return [self tryFromDatabaseWhere: @{@"device":CKKSNilToNSNull(device), @"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
215 }
216
217 + (instancetype)tryFromDatabaseFromCKRecordID:(CKRecordID*)recordID error:(NSError * __autoreleasing *)error {
218 return [self tryFromDatabaseWhere: @{@"device":CKKSNilToNSNull([self nameFromCKRecordID:recordID]), @"ckzone": CKKSNilToNSNull(recordID.zoneID.zoneName)} error:error];
219 }
220
221 + (NSArray<CKKSDeviceStateEntry*>*)allInZone:(CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *)error {
222 return [self allWhere:@{@"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
223 }
224
225 #pragma mark - CKKSCKRecordHolder methods
226
227 - (NSString*)CKRecordName {
228 return [NSString stringWithFormat:@"ckid-%@", self.device];
229 }
230
231 - (CKRecord*)updateCKRecord: (CKRecord*) record zoneID: (CKRecordZoneID*) zoneID {
232 if(![record.recordID.recordName isEqualToString: [self CKRecordName]]) {
233 @throw [NSException
234 exceptionWithName:@"WrongCKRecordNameException"
235 reason:[NSString stringWithFormat: @"CKRecord name (%@) was not %@", record.recordID.recordName, [self CKRecordName]]
236 userInfo:nil];
237 }
238 if(![record.recordType isEqualToString: SecCKRecordDeviceStateType]) {
239 @throw [NSException
240 exceptionWithName:@"WrongCKRecordTypeException"
241 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordDeviceStateType]
242 userInfo:nil];
243 }
244
245 record[SecCKSRecordOSVersionKey] = self.osVersion;
246 record[SecCKSRecordLastUnlockTime] = self.lastUnlockTime;
247
248 record[SecCKRecordOctagonPeerID] = self.octagonPeerID;
249 record[SecCKRecordOctagonStatus] = [self cliqueStatusToCKType:self.octagonStatus];
250
251 record[SecCKRecordCircleStatus] = [self sosCCStatusToCKType: self.circleStatus];
252 record[SecCKRecordKeyState] = CKKSZoneKeyToNumber(self.keyState);
253
254 record[SecCKRecordCirclePeerID] = self.circlePeerID;
255
256 #define CKKeyRef(uuid) (!uuid ? nil : [[CKReference alloc] initWithRecordID:[[CKRecordID alloc] initWithRecordName:uuid \
257 zoneID:self.zoneID] \
258 action: CKReferenceActionNone])
259
260 record[SecCKRecordCurrentTLK] = CKKeyRef(self.currentTLKUUID);
261 record[SecCKRecordCurrentClassA] = CKKeyRef(self.currentClassAUUID);
262 record[SecCKRecordCurrentClassC] = CKKeyRef(self.currentClassCUUID);
263
264 return record;
265 }
266
267 - (bool)matchesCKRecord: (CKRecord*) record {
268 if(![record.recordType isEqualToString: SecCKRecordDeviceStateType]) {
269 return false;
270 }
271
272 if(![record.recordID.recordName isEqualToString: [self CKRecordName]]) {
273 return false;
274 }
275
276 if((!(self.lastUnlockTime == nil && record[SecCKSRecordLastUnlockTime] == nil)) &&
277 ![record[SecCKSRecordLastUnlockTime] isEqual: self.lastUnlockTime]) {
278 return false;
279 }
280
281 if((!(self.osVersion == nil && record[SecCKSRecordOSVersionKey] == nil)) &&
282 ![record[SecCKSRecordOSVersionKey] isEqualToString: self.osVersion]) {
283 return false;
284 }
285
286 if((!(self.circlePeerID == nil && record[SecCKRecordCirclePeerID] == nil)) &&
287 ![record[SecCKRecordCirclePeerID] isEqualToString: self.circlePeerID]) {
288 return false;
289 }
290
291 if((!(self.octagonPeerID == nil && record[SecCKRecordOctagonPeerID] == nil)) &&
292 ![record[SecCKRecordOctagonPeerID] isEqualToString:self.octagonPeerID]) {
293 return false;
294 }
295
296 if((!(self.octagonStatus == nil && record[SecCKRecordOctagonStatus] == nil)) &&
297 [self.octagonStatus isEqual: [self cktypeToOTCliqueStatusWrapper:record[SecCKRecordOctagonStatus]]]) {
298 return false;
299 }
300
301 if([self cktypeToSOSCCStatus: record[SecCKRecordCircleStatus]] != self.circleStatus) {
302 return false;
303 }
304
305 if(![CKKSZoneKeyRecover(record[SecCKRecordKeyState]) isEqualToString: self.keyState]) {
306 return false;
307 }
308
309 if(![[[record[SecCKRecordCurrentTLK] recordID] recordName] isEqualToString: self.currentTLKUUID]) {
310 return false;
311 }
312 if(![[[record[SecCKRecordCurrentClassA] recordID] recordName] isEqualToString: self.currentTLKUUID]) {
313 return false;
314 }
315 if(![[[record[SecCKRecordCurrentClassC] recordID] recordName] isEqualToString: self.currentTLKUUID]) {
316 return false;
317 }
318
319 return true;
320 }
321
322 - (void)setFromCKRecord: (CKRecord*) record {
323 if(![record.recordType isEqualToString: SecCKRecordDeviceStateType]) {
324 @throw [NSException
325 exceptionWithName:@"WrongCKRecordTypeException"
326 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordDeviceStateType]
327 userInfo:nil];
328 }
329
330 [self setStoredCKRecord:record];
331
332 self.osVersion = record[SecCKSRecordOSVersionKey];
333 self.lastUnlockTime = record[SecCKSRecordLastUnlockTime];
334 self.device = [CKKSDeviceStateEntry nameFromCKRecordID: record.recordID];
335
336 self.octagonPeerID = record[SecCKRecordOctagonPeerID];
337 self.octagonStatus = [self cktypeToOTCliqueStatusWrapper:record[SecCKRecordOctagonStatus]];
338
339 self.circlePeerID = record[SecCKRecordCirclePeerID];
340
341 self.circleStatus = [self cktypeToSOSCCStatus:record[SecCKRecordCircleStatus]];
342
343 self.keyState = CKKSZoneKeyRecover(record[SecCKRecordKeyState]);
344
345 self.currentTLKUUID = [[record[SecCKRecordCurrentTLK] recordID] recordName];
346 self.currentClassAUUID = [[record[SecCKRecordCurrentClassA] recordID] recordName];
347 self.currentClassCUUID = [[record[SecCKRecordCurrentClassC] recordID] recordName];
348 }
349
350 #pragma mark - CKKSSQLDatabaseObject methods
351
352 + (NSString*)sqlTable {
353 return @"ckdevicestate";
354 }
355
356 + (NSArray<NSString*>*)sqlColumns {
357 return @[@"device", @"ckzone", @"osversion", @"lastunlock",
358 @"peerid", @"circlestatus",
359 @"octagonpeerid", @"octagonstatus",
360 @"keystate", @"currentTLK", @"currentClassA", @"currentClassC", @"ckrecord"];
361 }
362
363 - (NSDictionary<NSString*,NSString*>*)whereClauseToFindSelf {
364 return @{@"device":self.device, @"ckzone":self.zoneID.zoneName};
365 }
366
367 - (NSDictionary<NSString*,NSString*>*)sqlValues {
368 NSISO8601DateFormatter* dateFormat = [[NSISO8601DateFormatter alloc] init];
369
370 return @{@"device": self.device,
371 @"ckzone": CKKSNilToNSNull(self.zoneID.zoneName),
372 @"osversion": CKKSNilToNSNull(self.osVersion),
373 @"lastunlock": CKKSNilToNSNull(self.lastUnlockTime ? [dateFormat stringFromDate:self.lastUnlockTime] : nil),
374 @"peerid": CKKSNilToNSNull(self.circlePeerID),
375 @"circlestatus": (__bridge NSString*)SOSAccountGetSOSCCStatusString(self.circleStatus),
376 @"octagonpeerid": CKKSNilToNSNull(self.octagonPeerID),
377 @"octagonstatus": CKKSNilToNSNull(self.octagonStatus ? OTCliqueStatusToString(self.octagonStatus.status) : nil),
378 @"keystate": CKKSNilToNSNull(self.keyState),
379 @"currentTLK": CKKSNilToNSNull(self.currentTLKUUID),
380 @"currentClassA": CKKSNilToNSNull(self.currentClassAUUID),
381 @"currentClassC": CKKSNilToNSNull(self.currentClassCUUID),
382 @"ckrecord": CKKSNilToNSNull([self.encodedCKRecord base64EncodedStringWithOptions:0]),
383 };
384 }
385
386 + (instancetype)fromDatabaseRow:(NSDictionary<NSString*, CKKSSQLResult*>*)row {
387 OTCliqueStatusWrapper* octagonStatus = nil;
388 NSString* octagonStatusString = row[@"octagonstatus"].asString;
389 if(octagonStatusString) {
390 octagonStatus = [[OTCliqueStatusWrapper alloc] initWithStatus:OTCliqueStatusFromString(octagonStatusString)];
391 }
392
393 return [[CKKSDeviceStateEntry alloc] initForDevice:row[@"device"].asString
394 osVersion:row[@"osversion"].asString
395 lastUnlockTime:row[@"lastunlock"].asISO8601Date
396 octagonPeerID:row[@"octagonpeerid"].asString
397 octagonStatus:octagonStatus
398 circlePeerID:row[@"peerid"].asString
399 circleStatus:SOSAccountGetSOSCCStatusFromString((__bridge CFStringRef) row[@"circlestatus"].asString)
400 keyState:(CKKSZoneKeyState*)row[@"keystate"].asString
401 currentTLKUUID:row[@"currentTLK"].asString
402 currentClassAUUID:row[@"currentClassA"].asString
403 currentClassCUUID:row[@"currentClassC"].asString
404 zoneID:[[CKRecordZoneID alloc] initWithZoneName: row[@"ckzone"].asString ownerName:CKCurrentUserDefaultName]
405 encodedCKRecord:row[@"ckrecord"].asBase64DecodedData
406 ];
407 }
408
409 @end
410
411 #endif
412