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