]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSItem.m
Security-59754.41.1.tar.gz
[apple/security.git] / keychain / ckks / CKKSItem.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 #import "CKKSItem.h"
30 #import "CKKSSIV.h"
31
32 #include <utilities/SecDb.h>
33 #include "keychain/securityd/SecDbItem.h"
34 #include "keychain/securityd/SecItemSchema.h"
35
36 #import <CloudKit/CloudKit.h>
37 #import <CloudKit/CloudKit_Private.h>
38
39 @implementation CKKSItem
40
41 - (instancetype) initWithCKRecord: (CKRecord*) record {
42 if(self = [super initWithCKRecord: record]) {
43 }
44 return self;
45 }
46
47 - (instancetype) initCopyingCKKSItem: (CKKSItem*) item {
48 if(self = [super initWithCKRecordType: item.ckRecordType encodedCKRecord:item.encodedCKRecord zoneID:item.zoneID]) {
49 _uuid = item.uuid;
50 _parentKeyUUID = item.parentKeyUUID;
51 _generationCount = item.generationCount;
52 _encitem = item.encitem;
53 _wrappedkey = item.wrappedkey;
54 _encver = item.encver;
55
56 _plaintextPCSServiceIdentifier = item.plaintextPCSServiceIdentifier;
57 _plaintextPCSPublicKey = item.plaintextPCSPublicKey;
58 _plaintextPCSPublicIdentity = item.plaintextPCSPublicIdentity;
59 }
60 return self;
61 }
62
63 - (instancetype) initWithUUID: (NSString*) uuid
64 parentKeyUUID: (NSString*) parentKeyUUID
65 zoneID: (CKRecordZoneID*) zoneID
66 {
67 return [self initWithUUID:uuid
68 parentKeyUUID:parentKeyUUID
69 zoneID:zoneID
70 encodedCKRecord:nil
71 encItem:nil
72 wrappedkey:nil
73 generationCount:0
74 encver:CKKSItemEncryptionVersionNone];
75 }
76
77 - (instancetype) initWithUUID: (NSString*) uuid
78 parentKeyUUID: (NSString*) parentKeyUUID
79 zoneID: (CKRecordZoneID*) zoneID
80 encItem: (NSData*) encitem
81 wrappedkey: (CKKSWrappedAESSIVKey*) wrappedkey
82 generationCount: (NSUInteger) genCount
83 encver: (NSUInteger) encver
84 {
85 return [self initWithUUID:uuid
86 parentKeyUUID:parentKeyUUID
87 zoneID:zoneID
88 encodedCKRecord:nil
89 encItem:encitem
90 wrappedkey:wrappedkey
91 generationCount:genCount
92 encver:encver];
93 }
94
95 - (instancetype) initWithUUID: (NSString*) uuid
96 parentKeyUUID: (NSString*) parentKeyUUID
97 zoneID: (CKRecordZoneID*)zoneID
98 encodedCKRecord: (NSData*) encodedrecord
99 encItem: (NSData*) encitem
100 wrappedkey: (CKKSWrappedAESSIVKey*) wrappedkey
101 generationCount: (NSUInteger) genCount
102 encver: (NSUInteger) encver
103 {
104 return [self initWithUUID:uuid
105 parentKeyUUID:parentKeyUUID
106 zoneID:zoneID
107 encodedCKRecord:encodedrecord
108 encItem:encitem
109 wrappedkey:wrappedkey
110 generationCount:genCount
111 encver:encver
112 plaintextPCSServiceIdentifier:nil
113 plaintextPCSPublicKey:nil
114 plaintextPCSPublicIdentity:nil];
115 }
116
117 - (instancetype) initWithUUID: (NSString*) uuid
118 parentKeyUUID: (NSString*) parentKeyUUID
119 zoneID: (CKRecordZoneID*)zoneID
120 encodedCKRecord: (NSData*) encodedrecord
121 encItem: (NSData*) encitem
122 wrappedkey: (CKKSWrappedAESSIVKey*) wrappedkey
123 generationCount: (NSUInteger) genCount
124 encver: (NSUInteger) encver
125 plaintextPCSServiceIdentifier: (NSNumber*) pcsServiceIdentifier
126 plaintextPCSPublicKey: (NSData*) pcsPublicKey
127 plaintextPCSPublicIdentity: (NSData*) pcsPublicIdentity
128 {
129 if(self = [super initWithCKRecordType: SecCKRecordItemType encodedCKRecord:encodedrecord zoneID:zoneID]) {
130 _uuid = uuid;
131 _parentKeyUUID = parentKeyUUID;
132 _generationCount = genCount;
133 self.encitem = encitem;
134 _wrappedkey = wrappedkey;
135 _encver = encver;
136
137 _plaintextPCSServiceIdentifier = pcsServiceIdentifier;
138 _plaintextPCSPublicKey = pcsPublicKey;
139 _plaintextPCSPublicIdentity = pcsPublicIdentity;
140 }
141
142 return self;
143 }
144
145 - (BOOL)isEqual: (id) object {
146 if(![object isKindOfClass:[CKKSItem class]]) {
147 return NO;
148 }
149
150 CKKSItem* obj = (CKKSItem*) object;
151
152 return ([self.uuid isEqual: obj.uuid] &&
153 [self.parentKeyUUID isEqual: obj.parentKeyUUID] &&
154 [self.zoneID isEqual: obj.zoneID] &&
155 ((self.encitem == nil && obj.encitem == nil) || ([self.encitem isEqual: obj.encitem])) &&
156 [self.wrappedkey isEqual: obj.wrappedkey] &&
157 self.generationCount == obj.generationCount &&
158 self.encver == obj.encver &&
159 true) ? YES : NO;
160 }
161
162 #pragma mark - CKRecord handling
163
164 - (NSString*) CKRecordName {
165 return self.uuid;
166 }
167
168 - (void) setFromCKRecord: (CKRecord*) record {
169 if(![record.recordType isEqual: SecCKRecordItemType]) {
170 @throw [NSException
171 exceptionWithName:@"WrongCKRecordTypeException"
172 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordItemType]
173 userInfo:nil];
174 }
175
176 [self setStoredCKRecord:record];
177
178 _uuid = [[record recordID] recordName];
179 self.parentKeyUUID = [record[SecCKRecordParentKeyRefKey] recordID].recordName;
180 self.encitem = record[SecCKRecordDataKey];
181
182 // If wrapped key is nil, this is a bad record. We've seen this at least once, though, and so need to be resilient to it.
183 // Passing nil here will cause a crash, so pass all zeroes.
184 NSString* wrappedKey = record[SecCKRecordWrappedKeyKey];
185 if(wrappedKey) {
186 self.wrappedkey = [[CKKSWrappedAESSIVKey alloc] initWithBase64:wrappedKey];
187 } else {
188 ckkserror("ckksitem", record.recordID.zoneID, "Corrupt item recieved with no wrapped key");
189 self.wrappedkey = [CKKSWrappedAESSIVKey zeroedKey];
190 }
191
192 self.generationCount = [record[SecCKRecordGenerationCountKey] unsignedIntegerValue];
193 self.encver = [record[SecCKRecordEncryptionVersionKey] unsignedIntegerValue];
194
195 self.plaintextPCSServiceIdentifier = record[SecCKRecordPCSServiceIdentifier];
196 self.plaintextPCSPublicKey = record[SecCKRecordPCSPublicKey];
197 self.plaintextPCSPublicIdentity = record[SecCKRecordPCSPublicIdentity];
198 }
199
200 + (void)setOSVersionInRecord: (CKRecord*) record {
201 record[SecCKRecordHostOSVersionKey] = SecCKKSHostOSVersion();
202 }
203
204 - (CKRecord*) updateCKRecord: (CKRecord*) record zoneID: (CKRecordZoneID*) zoneID {
205 if(![record.recordType isEqual: SecCKRecordItemType]) {
206 @throw [NSException
207 exceptionWithName:@"WrongCKRecordTypeException"
208 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordItemType]
209 userInfo:nil];
210 }
211
212 // Items must have a wrapping key.
213 record[SecCKRecordParentKeyRefKey] = [[CKReference alloc] initWithRecordID: [[CKRecordID alloc] initWithRecordName: self.parentKeyUUID zoneID: zoneID] action: CKReferenceActionValidate];
214
215 [CKKSItem setOSVersionInRecord: record];
216
217 record[SecCKRecordDataKey] = self.encitem;
218 record[SecCKRecordWrappedKeyKey] = [self.wrappedkey base64WrappedKey];
219 record[SecCKRecordGenerationCountKey] = [NSNumber numberWithInteger:self.generationCount];
220 // TODO: if the record's generation count is already higher than ours, that's a problem.
221 record[SecCKRecordEncryptionVersionKey] = [NSNumber numberWithInteger:self.encver];
222
223 // Add unencrypted fields
224 record[SecCKRecordPCSServiceIdentifier] = self.plaintextPCSServiceIdentifier;
225 record[SecCKRecordPCSPublicKey] = self.plaintextPCSPublicKey;
226 record[SecCKRecordPCSPublicIdentity] = self.plaintextPCSPublicIdentity;
227
228 return record;
229 }
230
231
232 - (bool) matchesCKRecord: (CKRecord*) record {
233 if(![record.recordType isEqual: SecCKRecordItemType]) {
234 return false;
235 }
236
237 // We only really care about the data, the wrapped key, the generation count, and the parent key.
238 // Note that since all of those things are included as authenticated data into the AES-SIV ciphertext, we could just
239 // compare that. However, check 'em all.
240 if(![record.recordID.recordName isEqualToString: self.uuid]) {
241 ckksinfo_global("ckksitem", "UUID does not match");
242 return false;
243 }
244
245 if(![[record[SecCKRecordParentKeyRefKey] recordID].recordName isEqualToString: self.parentKeyUUID]) {
246 ckksinfo_global("ckksitem", "wrapping key reference does not match");
247 return false;
248 }
249
250 if(![record[SecCKRecordGenerationCountKey] isEqual: [NSNumber numberWithInteger:self.generationCount]]) {
251 ckksinfo_global("ckksitem", "SecCKRecordGenerationCountKey does not match");
252 return false;
253 }
254
255 if(![record[SecCKRecordWrappedKeyKey] isEqual: [self.wrappedkey base64WrappedKey]]) {
256 ckksinfo_global("ckksitem", "SecCKRecordWrappedKeyKey does not match");
257 return false;
258 }
259
260 if(![record[SecCKRecordDataKey] isEqual: self.encitem]) {
261 ckksinfo_global("ckksitem", "SecCKRecordDataKey does not match");
262 return false;
263 }
264
265 // Compare plaintext records, too
266 // Why is obj-c nullable equality so difficult?
267 if(!((record[SecCKRecordPCSServiceIdentifier] == nil && self.plaintextPCSServiceIdentifier == nil) ||
268 [record[SecCKRecordPCSServiceIdentifier] isEqual: self.plaintextPCSServiceIdentifier])) {
269 ckksinfo_global("ckksitem", "SecCKRecordPCSServiceIdentifier does not match");
270 return false;
271 }
272
273 if(!((record[SecCKRecordPCSPublicKey] == nil && self.plaintextPCSPublicKey == nil) ||
274 [record[SecCKRecordPCSPublicKey] isEqual: self.plaintextPCSPublicKey])) {
275 ckksinfo_global("ckksitem", "SecCKRecordPCSPublicKey does not match");
276 return false;
277 }
278
279 if(!((record[SecCKRecordPCSPublicIdentity] == nil && self.plaintextPCSPublicIdentity == nil) ||
280 [record[SecCKRecordPCSPublicIdentity] isEqual: self.plaintextPCSPublicIdentity])) {
281 ckksinfo_global("ckksitem", "SecCKRecordPCSPublicIdentity does not match");
282 return false;
283 }
284
285 return true;
286 }
287
288 // Generates the list of 'authenticated data' to go along with this item, and optionally adds in unknown, future fields received from CloudKit
289 - (NSDictionary<NSString*, NSData*>*)makeAuthenticatedDataDictionaryUpdatingCKKSItem:(CKKSItem*) olditem encryptionVersion:(SecCKKSItemEncryptionVersion)encversion {
290 switch(encversion) {
291 case CKKSItemEncryptionVersion1:
292 return [self makeAuthenticatedDataDictionaryUpdatingCKKSItemEncVer1];
293 case CKKSItemEncryptionVersion2:
294 return [self makeAuthenticatedDataDictionaryUpdatingCKKSItemEncVer2:olditem];
295 default:
296 @throw [NSException
297 exceptionWithName:@"WrongEncryptionVersionException"
298 reason:[NSString stringWithFormat: @"%d is not a known encryption version", (int)encversion]
299 userInfo:nil];
300 }
301 }
302
303 - (NSDictionary<NSString*, NSData*>*)makeAuthenticatedDataDictionaryUpdatingCKKSItemEncVer1 {
304 NSMutableDictionary<NSString*, NSData*>* authenticatedData = [[NSMutableDictionary alloc] init];
305
306 authenticatedData[@"UUID"] = [self.uuid dataUsingEncoding: NSUTF8StringEncoding];
307 authenticatedData[SecCKRecordWrappedKeyKey] = [self.parentKeyUUID dataUsingEncoding: NSUTF8StringEncoding];
308
309 uint64_t genCount64 = OSSwapHostToLittleConstInt64(self.generationCount);
310 authenticatedData[SecCKRecordGenerationCountKey] = [NSData dataWithBytes:&genCount64 length:sizeof(genCount64)];
311
312 uint64_t encver = OSSwapHostToLittleConstInt64((uint64_t)self.encver);
313 authenticatedData[SecCKRecordEncryptionVersionKey] = [NSData dataWithBytes:&encver length:sizeof(encver)];
314
315 // In v1, don't authenticate the plaintext PCS fields
316 authenticatedData[SecCKRecordPCSServiceIdentifier] = nil;
317 authenticatedData[SecCKRecordPCSPublicKey] = nil;
318 authenticatedData[SecCKRecordPCSPublicIdentity] = nil;
319
320 return authenticatedData;
321 }
322
323 - (NSDictionary<NSString*, NSData*>*)makeAuthenticatedDataDictionaryUpdatingCKKSItemEncVer2:(CKKSItem*) olditem {
324 NSMutableDictionary<NSString*, NSData*>* authenticatedData = [[NSMutableDictionary alloc] init];
325
326 authenticatedData[@"UUID"] = [self.uuid dataUsingEncoding: NSUTF8StringEncoding];
327 authenticatedData[SecCKRecordWrappedKeyKey] = [self.parentKeyUUID dataUsingEncoding: NSUTF8StringEncoding];
328
329 uint64_t genCount64 = OSSwapHostToLittleConstInt64(self.generationCount);
330 authenticatedData[SecCKRecordGenerationCountKey] = [NSData dataWithBytes:&genCount64 length:sizeof(genCount64)];
331
332 uint64_t encver = OSSwapHostToLittleConstInt64((uint64_t)self.encver);
333 authenticatedData[SecCKRecordEncryptionVersionKey] = [NSData dataWithBytes:&encver length:sizeof(encver)];
334
335 // v2 authenticates the PCS fields too
336 if(self.plaintextPCSServiceIdentifier) {
337 uint64_t pcsServiceIdentifier = OSSwapHostToLittleConstInt64([self.plaintextPCSServiceIdentifier unsignedLongValue]);
338 authenticatedData[SecCKRecordPCSServiceIdentifier] = [NSData dataWithBytes:&pcsServiceIdentifier length:sizeof(pcsServiceIdentifier)];
339 }
340 authenticatedData[SecCKRecordPCSPublicKey] = self.plaintextPCSPublicKey;
341 authenticatedData[SecCKRecordPCSPublicIdentity] = self.plaintextPCSPublicIdentity;
342
343 // Iterate through the fields in the old CKKSItem. If we don't recognize any of them, add them to the authenticated data.
344 if(olditem) {
345 CKRecord* record = olditem.storedCKRecord;
346 if(record) {
347 for(NSString* key in record.allKeys) {
348 if([key isEqualToString:@"UUID"] ||
349 [key isEqualToString:SecCKRecordHostOSVersionKey] ||
350 [key isEqualToString:SecCKRecordDataKey] ||
351 [key isEqualToString:SecCKRecordWrappedKeyKey] ||
352 [key isEqualToString:SecCKRecordGenerationCountKey] ||
353 [key isEqualToString:SecCKRecordEncryptionVersionKey] ||
354 [key isEqualToString:SecCKRecordPCSServiceIdentifier] ||
355 [key isEqualToString:SecCKRecordPCSPublicKey] ||
356 [key isEqualToString:SecCKRecordPCSPublicIdentity]) {
357 // This version of CKKS knows about this data field. Ignore them with prejudice.
358 continue;
359 }
360
361 if([key hasPrefix:@"server_"]) {
362 // Ignore all fields prefixed by "server_"
363 continue;
364 }
365
366 id obj = record[key];
367
368 // Skip CKReferences, NSArray, CLLocation, and CKAsset.
369 if([obj isKindOfClass: [NSString class]]) {
370 // Add an NSString.
371 authenticatedData[key] = [obj dataUsingEncoding: NSUTF8StringEncoding];
372 } else if([obj isKindOfClass: [NSData class]]) {
373 // Add an NSData
374 authenticatedData[key] = [obj copy];
375 } else if([obj isKindOfClass:[NSDate class]]) {
376 // Add an NSDate
377 NSISO8601DateFormatter *formatter = [[NSISO8601DateFormatter alloc] init];
378 NSString* str = [formatter stringForObjectValue: obj];
379
380 authenticatedData[key] = [str dataUsingEncoding: NSUTF8StringEncoding];
381 } else if([obj isKindOfClass: [NSNumber class]]) {
382 // Add an NSNumber
383 uint64_t n64 = OSSwapHostToLittleConstInt64([obj unsignedLongLongValue]);
384 authenticatedData[key] = [NSData dataWithBytes:&n64 length:sizeof(n64)];
385 }
386 }
387
388 }
389 }
390
391 // TODO: add unauth'ed field name here
392
393 return authenticatedData;
394 }
395
396 #pragma mark - Utility
397
398 - (NSString*)description {
399 return [NSString stringWithFormat: @"<%@: %@>", NSStringFromClass([self class]), self.uuid];
400 }
401
402 - (NSString*)debugDescription {
403 return [NSString stringWithFormat: @"<%@: %@ %p>", NSStringFromClass([self class]), self.uuid, self];
404 }
405
406 - (instancetype)copyWithZone:(NSZone *)zone {
407 CKKSItem *itemCopy = [super copyWithZone:zone];
408 itemCopy->_uuid = _uuid;
409 itemCopy->_parentKeyUUID = _parentKeyUUID;
410 itemCopy->_encitem = _encitem;
411 itemCopy->_wrappedkey = _wrappedkey;
412 itemCopy->_generationCount = _generationCount;
413 itemCopy->_encver = _encver;
414 return itemCopy;
415 }
416
417 #pragma mark - Getters/Setters
418
419 - (NSString*) base64Item {
420 return [self.encitem base64EncodedStringWithOptions:0];
421 }
422
423 - (void) setBase64Item: (NSString*) base64Item {
424 _encitem = [[NSData alloc] initWithBase64EncodedString: base64Item options:0];
425 }
426
427 #pragma mark - CKKSSQLDatabaseObject helpers
428
429 // Note that CKKSItems are not intended to be saved directly, and so CKKSItem does not implement sqlTable.
430 // You must subclass CKKSItem to have this work correctly, although you can call back up into this class to use these if you like.
431
432 + (NSArray<NSString*>*)sqlColumns {
433 return @[@"UUID", @"parentKeyUUID", @"ckzone", @"encitem", @"wrappedkey", @"gencount", @"encver", @"ckrecord",
434 @"pcss", @"pcsk", @"pcsi"];
435 }
436
437 - (NSDictionary<NSString*,NSString*>*)whereClauseToFindSelf {
438 return @{@"UUID": self.uuid, @"ckzone":self.zoneID.zoneName};
439 }
440
441 - (NSDictionary<NSString*,NSString*>*)sqlValues {
442 return @{@"UUID": self.uuid,
443 @"parentKeyUUID": self.parentKeyUUID,
444 @"ckzone": CKKSNilToNSNull(self.zoneID.zoneName),
445 @"encitem": self.base64encitem,
446 @"wrappedkey": [self.wrappedkey base64WrappedKey],
447 @"gencount": [[NSNumber numberWithInteger:self.generationCount] stringValue],
448 @"encver": [[NSNumber numberWithInteger:self.encver] stringValue],
449 @"ckrecord": CKKSNilToNSNull([self.encodedCKRecord base64EncodedStringWithOptions:0]),
450 @"pcss": CKKSNilToNSNull(self.plaintextPCSServiceIdentifier),
451 @"pcsk": CKKSNilToNSNull([self.plaintextPCSPublicKey base64EncodedStringWithOptions:0]),
452 @"pcsi": CKKSNilToNSNull([self.plaintextPCSPublicIdentity base64EncodedStringWithOptions:0])};
453 }
454
455 + (instancetype)fromDatabaseRow:(NSDictionary<NSString*, CKKSSQLResult*>*)row {
456 return [[CKKSItem alloc] initWithUUID:row[@"UUID"].asString
457 parentKeyUUID:row[@"parentKeyUUID"].asString
458 zoneID:[[CKRecordZoneID alloc] initWithZoneName: row[@"ckzone"].asString ownerName:CKCurrentUserDefaultName]
459 encodedCKRecord:row[@"ckrecord"].asBase64DecodedData
460 encItem:row[@"encitem"].asBase64DecodedData
461 wrappedkey:row[@"wrappedkey"].asString == nil ? nil : [[CKKSWrappedAESSIVKey alloc] initWithBase64:row[@"wrappedkey"].asString]
462 generationCount:row[@"gencount"].asNSInteger
463 encver:row[@"encver"].asNSInteger
464 plaintextPCSServiceIdentifier:row[@"pcss"].asNSNumberInteger
465 plaintextPCSPublicKey:row[@"pcsk"].asBase64DecodedData
466 plaintextPCSPublicIdentity:row[@"pcsi"].asBase64DecodedData
467 ];
468 }
469
470 @end
471
472 #pragma mark - CK-Aware Database Helpers
473
474 @implementation CKKSSQLDatabaseObject (CKKSZoneExtras)
475
476 + (NSArray<NSString*>*)allUUIDs:(CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *)error {
477 __block NSMutableArray<NSString*>* uuids = [[NSMutableArray alloc] init];
478
479 [CKKSSQLDatabaseObject queryDatabaseTable: [self sqlTable]
480 where:@{@"ckzone": CKKSNilToNSNull(zoneID.zoneName)}
481 columns: @[@"UUID"]
482 groupBy: nil
483 orderBy:nil
484 limit: -1
485 processRow:^(NSDictionary<NSString*, CKKSSQLResult*>* row) {
486 [uuids addObject: row[@"UUID"].asString];
487 }
488 error: error];
489 return uuids;
490 }
491
492 + (NSArray*) all:(CKRecordZoneID*) zoneID error: (NSError * __autoreleasing *) error {
493 return [self allWhere: @{@"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
494 }
495
496 + (bool) deleteAll:(CKRecordZoneID*) zoneID error: (NSError * __autoreleasing *) error {
497 bool ok = [CKKSSQLDatabaseObject deleteFromTable:[self sqlTable] where: @{@"ckzone":CKKSNilToNSNull(zoneID.zoneName)} connection:nil error: error];
498
499 if(ok) {
500 secdebug("ckksitem", "Deleted all %@", self);
501 } else {
502 secdebug("ckksitem", "Couldn't delete all %@: %@", self, error ? *error : @"unknown");
503 }
504 return ok;
505 }
506
507 @end
508
509 #endif